Index: main/kernel/src/org/ametys/runtime/config/ConfigManager.java
===================================================================
--- main/kernel/src/org/ametys/runtime/config/ConfigManager.java	(revision 32647)
+++ main/kernel/src/org/ametys/runtime/config/ConfigManager.java	(working copy)
@@ -731,9 +731,9 @@
         _saxConfiguration(handler);
         XMLUtils.endElement(handler, "configuration");
         
-        XMLUtils.startElement(handler, "values");
+        XMLUtils.startElement(handler, "configuration-values");
         _saxValues(handler);
-        XMLUtils.endElement(handler, "values");
+        XMLUtils.endElement(handler, "configuration-values");
     }
 
     /**
@@ -879,11 +879,9 @@
                 {
                     switchId = group.getSwitch();
                     ConfigParameter switcher = get(switchId);
-                    
-                    
+
                     XMLUtils.startElement(contentHandler, "switcher");
-                    XMLUtils.createElement(contentHandler, "id", switchId); // FIXME
-                    // XMLUtils.createElement(contentHandler, "id", switchId.replace('.', '_')); // FIXME
+                    XMLUtils.createElement(contentHandler, "id", switchId); 
                     switcher.getLabel().toSAX(contentHandler, "label");
                     XMLUtils.createElement(contentHandler, "defaultValue", ParameterHelper.valueToString(switcher.getDefaultValue()));
                     XMLUtils.endElement(contentHandler, "switcher");
@@ -894,8 +892,7 @@
                 {
                     String paramId = param.getId();
                     
-                    XMLUtils.startElement(contentHandler, paramId);   // FIXME
-                    //XMLUtils.startElement(contentHandler, paramId.replace('.', '_')); // FIXME
+                    XMLUtils.startElement(contentHandler, paramId);   
                     
                     ParameterHelper.toSAXParameterInternal(contentHandler, param, null);
                     
@@ -916,8 +913,7 @@
                         }
                     }
                     
-                    XMLUtils.endElement(contentHandler, paramId); // FIXME
-                    // XMLUtils.endElement(contentHandler, paramId.replace('.', '_')); // FIXME
+                    XMLUtils.endElement(contentHandler, paramId); 
                 }
                 XMLUtils.endElement(contentHandler, "elements");
 
@@ -947,15 +943,14 @@
             untypedValues = new HashMap<>();
         }
         
-        XMLUtils.startElement(contentHandler, "metadata");
+        XMLUtils.startElement(contentHandler, "values");
         for (String parameterId : _params.keySet())
         {
             ConfigParameter param = _params.get(parameterId);
             Object value = _getValue (parameterId, param.getType(), untypedValues);
-            XMLUtils.createElement(contentHandler, parameterId, ParameterHelper.valueToString(value)); //FIXME
-//            XMLUtils.createElement(contentHandler, parameterId.replace('.', '_'), ParameterHelper.valueToString(value)); //FIXME
+            XMLUtils.createElement(contentHandler, parameterId, ParameterHelper.valueToString(value)); 
         }
-        XMLUtils.endElement(contentHandler, "metadata");
+        XMLUtils.endElement(contentHandler, "values");
     }
     
     private void _saxParametersOLD(ContentHandler handler) throws SAXException, ProcessingException // FIXME remove
@@ -1116,7 +1111,7 @@
      * Values are untyped (all are of type String) and might be null.
      * @param untypedValues A map (key, untyped value).
      * @param fileName the config file absolute path
-     * @throws Exception If an error occured while saving values
+     * @throws Exception If an error occurred while saving values
      */
     public void save(Map<String, String> untypedValues, String fileName) throws Exception
     {
Index: main/plugin-admin/i18n/messages_en.xml
===================================================================
--- main/plugin-admin/i18n/messages_en.xml	(revision 32647)
+++ main/plugin-admin/i18n/messages_en.xml	(working copy)
@@ -48,11 +48,10 @@
     <message key="PLUGINS_ADMIN_CONFIG_SAVE_WAIT_MSG">Saving. Please wait ...</message>
     <message key="PLUGINS_ADMIN_CONFIG_SAVE_FATALERROR">Server is not responding or a fatal error occurred during saving.\nConsult applications and servlet engines logfiles for more details.</message>
     <message key="PLUGINS_ADMIN_CONFIG_SAVE_ERROR">An error occurred on the server and parameters have not been updated.\n\nInternal error message is :\n</message>
-    <message key="PLUGINS_ADMIN_CONFIG_SAVE_NOFORM_TITLE">No form</message>
-    <message key="PLUGINS_ADMIN_CONFIG_SAVE_NOFORM">There is no form to save.</message>
-    <message key="PLUGINS_ADMIN_CONFIG_SAVE_INVALID_TITLE">The form contains errors</message>
     <message key="PLUGINS_ADMIN_CONFIG_SAVE_INVALID">One or more fields in the form are incorrect.Please modify them and then save the form.</message>
     <message key="PLUGINS_ADMIN_CONFIG_INVALID_REGEXP">Wrong value. Expected format:</message> 
+    <message key="PLUGINS_ADMIN_CONFIG_DIRTY_CLOSE_MBOX_LABEL">Close without saving</message>
+    <message key="PLUGINS_ADMIN_CONFIG_DIRTY_CLOSE_MBOX_TEXT">Caution!&lt;br/&gt;If you quit now all your modifications will be lost !&lt;br/&gt;Do you wish to discard your modifications ?</message>
     <message key="DATE_FIELD_FORMAT">m/d/Y</message>
     
     <message key="PLUGINS_ADMIN_CONFIG_PARAM_CHECK_ERROR">Error while performing the parameter verification.</message>
@@ -60,11 +59,6 @@
     <message key="PLUGINS_ADMIN_CONFIG_PARAM_CHECK_UI_REF_CATEGORY">On the category</message>
     <message key="PLUGINS_ADMIN_CONFIG_PARAM_CHECK_UI_REF_GROUP">On the group</message>
     <message key="PLUGINS_ADMIN_CONFIG_PARAM_CHECK_UI_REF_PARAMETER">On the parameter</message>
-    <message key="PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_TITLE">Test failure</message>
-    <message key="PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_MSG">Before saving, it is recommended that all verifications pass.&lt;br/&gt;What do you want to do ?</message>
-    <message key="PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_SAVE">Save without verifying</message>
-    <message key="PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_RETRY">Run the verifications and save</message>
-    <message key="PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_CANCEL">Cancel</message>
     <message key="PLUGINS_ADMIN_CONFIG_PARAM_CHECKER_STATUS">Status: </message>
     <message key="PLUGINS_ADMIN_CONFIG_PARAM_CHECKER_STATUS_SUCCESS">Success</message>
     <message key="PLUGINS_ADMIN_CONFIG_PARAM_CHECKER_STATUS_FAILURE">Failure</message>
Index: main/plugin-admin/i18n/messages_fr.xml
===================================================================
--- main/plugin-admin/i18n/messages_fr.xml	(revision 32647)
+++ main/plugin-admin/i18n/messages_fr.xml	(working copy)
@@ -48,11 +48,9 @@
     <message key="PLUGINS_ADMIN_CONFIG_SAVE_WAIT_MSG">Sauvegarde en cours.&lt;br/&gt;Veuillez patienter ...</message>
     <message key="PLUGINS_ADMIN_CONFIG_SAVE_FATALERROR">Le serveur ne répond pas ou une erreur grave est survenue lors de la sauvegarde.\nConsultez les journaux de l'application et du moteur de servlet pour plus de détails.</message>
     <message key="PLUGINS_ADMIN_CONFIG_SAVE_ERROR">Une erreur est survenue sur le serveur et les paramètres n'ont pas été sauvegardés.\n\nLe message d'erreur interne est :\n</message>
-    <message key="PLUGINS_ADMIN_CONFIG_SAVE_NOFORM_TITLE">Pas de formulaire</message>
-    <message key="PLUGINS_ADMIN_CONFIG_SAVE_NOFORM">Impossible de trouver le formulaire à sauvegarder.</message>
-    <message key="PLUGINS_ADMIN_CONFIG_SAVE_INVALID_TITLE">Le formulaire contient des erreurs</message>
-    <message key="PLUGINS_ADMIN_CONFIG_SAVE_INVALID">Un ou plusieurs champs du formulaire sont incorrects. Veuillez les corriger avant de recommencer une sauvegarde.</message>
     <message key="PLUGINS_ADMIN_CONFIG_INVALID_REGEXP">La saisie ne respecte pas le format attendu :</message>
+    <message key="PLUGINS_ADMIN_CONFIG_DIRTY_CLOSE_MBOX_LABEL">Fermer sans enregistrer</message>
+    <message key="PLUGINS_ADMIN_CONFIG_DIRTY_CLOSE_MBOX_TEXT">Attention !&lt;br/&gt;Si vous continuez toutes vos modifications seront perdues !&lt;br/&gt;Souhaitez-vous abandonner vos modifications ?</message>
     <message key="DATE_FIELD_FORMAT">d/m/Y</message>
     
     <message key="PLUGINS_ADMIN_CONFIG_PARAM_CHECK_ERROR">Erreur lors de la vérification des paramètres</message>
@@ -60,11 +58,6 @@
     <message key="PLUGINS_ADMIN_CONFIG_PARAM_CHECK_UI_REF_CATEGORY">Sur la catégorie</message>
     <message key="PLUGINS_ADMIN_CONFIG_PARAM_CHECK_UI_REF_GROUP">Sur le groupe</message>
     <message key="PLUGINS_ADMIN_CONFIG_PARAM_CHECK_UI_REF_PARAMETER">Sur le paramètre</message>
-    <message key="PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_TITLE">Vérifications non-concluantes</message>
-    <message key="PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_MSG">Avant de sauver, il est recommandé que toutes les vérifications passent avec succès (un mauvais paramètre peut entrainer le blocage du démarrage d'Ametys).&lt;br/&gt;Que souhaitez-vous faire ?</message>
-    <message key="PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_SAVE">Sauver sans vérifier les paramètres</message>
-    <message key="PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_RETRY">Lancer les vérifications et sauver</message>
-    <message key="PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_CANCEL">Annuler</message>
     <message key="PLUGINS_ADMIN_CONFIG_PARAM_CHECKER_STATUS">Statut : </message>
     <message key="PLUGINS_ADMIN_CONFIG_PARAM_CHECKER_STATUS_SUCCESS">Succès</message>
     <message key="PLUGINS_ADMIN_CONFIG_PARAM_CHECKER_STATUS_FAILURE">Echec</message>
@@ -92,6 +85,7 @@
     <message key="PLUGINS_ADMIN_ERROR_DIALOG_TITLE">Erreur</message>
     <message key="PLUGINS_ADMIN_SAVE_DIALOG_TITLE">Sauvegarde</message>
     
+    
     <!-- ADMINISTRATOR JVMSTATUS -->
     <message key="PLUGINS_ADMIN_TOOL_JVMSTATUS_LABEL">Général</message>
     <message key="PLUGINS_ADMIN_TOOL_JVMSTATUS_DESC">Cet outil vous permet de consulter l'état général du système et de la JVM</message>
Index: main/plugin-admin/plugin.xml
===================================================================
--- main/plugin-admin/plugin.xml	(revision 32647)
+++ main/plugin-admin/plugin.xml	(working copy)
@@ -1040,6 +1040,7 @@
                 </class>
                 <scripts>
                     <file>js/Ametys/plugins/admin/config/SaveConfigAction.js</file>
+                    <file plugin="core-ui">js/Ametys/form/SaveHelper.js</file>
                 </scripts>
             </extension>
         </extensions>
Index: main/plugin-admin/resources/js/Ametys/plugins/admin/config/ConfigTool.js
===================================================================
--- main/plugin-admin/resources/js/Ametys/plugins/admin/config/ConfigTool.js	(revision 32647)
+++ main/plugin-admin/resources/js/Ametys/plugins/admin/config/ConfigTool.js	(working copy)
@@ -24,8 +24,10 @@
 	
 	/**
 	 * @private
-	 * @property {Ametys.Form.ConfigurableFormPanel} _formPanel The configuration form panel 
+	 * @property {Ametys.form.ConfigurableFormPanel} _formPanel The configuration form panel 
+	 *
 	 */
+	
 	getMBSelectionInteraction: function() 
 	{
 		return Ametys.tool.Tool.MB_TYPE_ACTIVE;
@@ -37,8 +39,7 @@
 			cls: 'config',
 			
 			listeners: {
-				'inputfocus': Ext.bind(this.sendCurrentSelection, this),
-				'toolopen': Ext.bind(this.sendCurrentSelection, this)
+				'inputfocus': Ext.bind(this.sendCurrentSelection, this)
 			}
 		});
 		
@@ -60,6 +61,32 @@
         });
 	},
 	
+	onOpen: function()
+	{
+		this.sendCurrentSelection();
+	},
+	
+	close: function(manual)
+	{
+		var me = this;
+        if (this.isDirty())
+		{
+			Ametys.Msg.confirm("<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_DIRTY_CLOSE_MBOX_LABEL'/>", 
+				"<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_DIRTY_CLOSE_MBOX_TEXT'/>", 
+				function (answer) {
+	                if (answer == 'yes')
+	                {
+	                	Ametys.plugins.admin.config.ConfigTool.superclass.close.call(me, [manual]);
+	                }
+	            },
+	            this
+			);
+			return;
+		}
+
+        this.callParent(arguments);
+	},
+	
 	/**
 	 * Get the configuration parameters in order to initialize the form panel with a server call
 	 */
@@ -89,7 +116,7 @@
 	{
 		// Initialize the form panel
 		this._formPanel.configure(Ext.dom.Query.select("config/configuration", response)[0]);
-		this._formPanel.setValues(Ext.dom.Query.select("config/values", response)[0]);
+		this._formPanel.setValues(Ext.dom.Query.select("config/configuration-values", response)[0], "values", "comments", "invalid");
 	}
 });
 
Index: main/plugin-admin/resources/js/Ametys/plugins/admin/config/SaveConfigAction.js
===================================================================
--- main/plugin-admin/resources/js/Ametys/plugins/admin/config/SaveConfigAction.js	(revision 32647)
+++ main/plugin-admin/resources/js/Ametys/plugins/admin/config/SaveConfigAction.js	(working copy)
@@ -20,100 +20,37 @@
  */
 Ext.define('Ametys.plugins.admin.config.SaveConfigAction', {
     singleton: true,
-    
-    /**
-     * @property {Number} LAUNCH_TESTS_SAVE_MBOX_HEIGHT The height and of the "launch tests and save" message box
-     * @private
-     * @readonly 
-     */
-    LAUNCH_TESTS_SAVE_MBOX_HEIGHT: 130,
-    
+
     /**
      * Save the configuration
      * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
      */
     save: function(controller)
     {
-        var target = controller.getMatchingTargets()[0];
-        if (target != null)
-        {
-            var subtargets = target.getSubtargets(function(target) { return target.getType() ==  Ametys.message.MessageTarget.FORM}, 1)
-            if (subtargets.length == 0)
-            {
-                Ext.MessageBox.alert("<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_SAVE_NOFORM_TITLE'/>", "<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_SAVE_NOFORM'/>");
-                return;
-            }
-            var subtarget = subtargets[0]; 
-            
-            var me = this,
-                testsOk = true,
-                form = subtarget.getParameters().form,
-                paramCheckersDAO = form._paramCheckersDAO,
-                paramCheckers = paramCheckersDAO._paramCheckers;
-                
-            // We save only when the form is valid
-            if (!form.getForm().isValid())
-            {
-                Ext.MessageBox.alert("<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_SAVE_INVALID_TITLE'/>", "<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_SAVE_INVALID'/>");
-                form.getForm().markInvalid();
-                return;
-            }
-            
-            function doSave()
-            {
-                Ext.getBody().mask("<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_SAVE_WAIT_MSG'/>");
-                Ext.defer(Ext.bind(me._save, me, [form.getValues()], false), 1, me);
-            }
-
-
-            // If all tests are not OK, we show a dialogbox to inform the user 
-            Ext.Array.each(paramCheckers, function(paramChecker) {
-                var status = paramChecker.getStatus();
-                
-                if (Ext.getCmp(paramChecker.buttonId).isVisible() 
-                    && (status == Ametys.form.ConfigurableFormPanel.ParameterChecker.STATUS_FAILURE 
-                        || status == Ametys.form.ConfigurableFormPanel.ParameterChecker.STATUS_NOT_TESTED
-                        || status == Ametys.form.ConfigurableFormPanel.ParameterChecker.STATUS_WARNING))
-                {
-                    testsOk = false;
-                    return false;
-                }
-            });
-            
-            if (!testsOk)
-            {
-                var msgBox = new Ext.window.MessageBox({closeAction: 'destroy'});
-                
-                msgBox.buttonText.yes = "<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_SAVE'/>";
-                msgBox.buttonText.no = "<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_RETRY'/>";
-                msgBox.buttonText.cancel = "<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_CANCEL'/>";
-                msgBox.show({
-                            title: "<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_TITLE'/>", 
-                            msg: "<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_TESTS_NOK_MBOX_MSG'/>",
-                            buttons: Ext.Msg.YESNOCANCEL,
-                            icon: Ext.Msg.ERROR,
-                            height: me.LAUNCH_TESTS_SAVE_MBOX_HEIGHT,
-                            fn: function(btn)
-                            {
-                                if (btn == 'yes')
-                                {
-                                    doSave();
-                                }
-                                else if (btn == 'no')
-                                {
-                                    Ext.getBody().mask("<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_SAVE_WAIT_MSG'/>");
-                                    Ametys.form.ConfigurableFormPanel.ParameterCheckersActions._check(paramCheckers, form, true, Ext.bind(function(success) { Ext.getBody().unmask(); if (success) { this.save(); } } , me), false);
-                                }
-                            }
-                        });
-                return;
-            }
-
-            doSave();
+    	var form = Ametys.form.SaveHelper.getForm(controller.getMatchingTargets()); 
+    	if (form != null)
+		{
+            Ametys.form.SaveHelper.canSave(form, Ext.bind(this._doSave, this, [form], 1));
         }
     },
     
     /**
+     * Trigger the save process if it was authorized
+     * @param {Boolean} canSave true if the saving is allowed, false otherwise
+     * @param {Ametys.form.ConfigurableFormPanel} form the form panel
+     */
+    _doSave: function(canSave, form)
+    {
+    	if (!canSave)
+		{
+			return;
+		}
+    	
+    	Ext.getBody().mask("<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_SAVE_WAIT_MSG'/>");
+        Ext.defer(Ext.bind(this._save, this, [form.getValues()], false), 1, this);
+    },
+
+    /**
      * The actual saving process, calling the server side
      * @param {Object} params the parameters used by the server
      */
@@ -145,18 +82,20 @@
         }
         result = result.responseXML;
         
-        var error = Ext.dom.Query.selectValue("*/error", result);
-        if (error != null && error != "")
+        // Server error
+        var error = Ext.dom.Query.selectValue("* > error", result);
+        if (!Ext.isEmpty(error))
         {
-            Ametys.Msg.show ({
-                title: "<i18n:text i18n:key='PLUGINS_ADMIN_SAVE_DIALOG_TITLE'/>",
-                msg: "<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_SAVE_ERROR'/>",
-                buttons: Ext.Msg.OK,
-                icon: Ext.MessageBox.ERROR
+        	Ametys.log.ErrorDialog.display({
+                title: "<i18n:text i18n:key='PLUGINS_ADMIN_SAVE_DIALOG_TITLE'/>", 
+                text: "<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_SAVE_ERROR'/>",
+                details: error,
+                category: "Ametys.plugins.core.administration.Config.save"
             });
             return;
         }
         
+        // Success
         Ametys.Msg.show ({
                 title: "<i18n:text i18n:key='PLUGINS_ADMIN_SAVE_DIALOG_TITLE'/>",
                 msg: "<i18n:text i18n:key='PLUGINS_ADMIN_CONFIG_SAVE_OK'/>",
Index: main/plugin-core-impl/i18n/messages_en.xml
===================================================================
--- main/plugin-core-impl/i18n/messages_en.xml	(revision 32647)
+++ main/plugin-core-impl/i18n/messages_en.xml	(working copy)
@@ -37,7 +37,7 @@
     <message key="PLUGINS_CORE_DATASOURCE_CORE_CONFIG_PASSWORD_DESCRIPTION">Password associated with the user name for connecting to the database</message>
     <message key="PLUGINS_CORE_DATASOURCE_CORE_CONFIG_CATEGORY_LDAP">LDAP directory</message>
     <message key="PLUGINS_CORE_SQL_CONNECTION_CHECKER_LABEL">Connection to the kernel database</message>
-    <message key="PLUGINS_CORE_SQL_CONNECTION_CHECKER_DESC">This test checks if the given information allow the connection to the SQL kernel database.</message>
+    <message key="PLUGINS_CORE_SQL_CONNECTION_CHECKER_DESC">This test checks if the given information allows the connection to the SQL kernel database.</message>
     
     <!-- DATASOURCE LDAP -->
     <message key="PLUGINS_CORE_DATASOURCE_CORE_LDAP_CONFIG_CATEGORY_LDAP_GROUP_CORE">Server</message>
Index: main/plugin-core-impl/plugin.xml
===================================================================
--- main/plugin-core-impl/plugin.xml	(revision 32647)
+++ main/plugin-core-impl/plugin.xml	(working copy)
@@ -603,7 +603,7 @@
             </linked-params>
         </param-checker>
         
-        <param-checker id="sql-connection-checker" class="org.ametys.plugins.core.impl.checker.SqlConnectionChecker">
+        <param-checker id="sql-connection-checker-kernel" class="org.ametys.plugins.core.impl.checker.SqlConnectionChecker">
             <label i18n="true">PLUGINS_CORE_SQL_CONNECTION_CHECKER_LABEL</label>
             <description i18n="true">PLUGINS_CORE_SQL_CONNECTION_CHECKER_DESC</description>
             <icon-large>img/config/check/test_sqlconnect_48.png</icon-large>
@@ -613,7 +613,7 @@
             <ui-ref>
                 <order>1</order>
                 <category i18n="true">PLUGINS_CORE_DATASOURCE_CORE_CONFIG_JDBC_CATEGORY_DATASOURCE</category>
-               <!-- <group i18n="true">PLUGINS_CORE_DATASOURCE_CORE_CONFIG_JDBC_CATEGORY_DATASOURCE_GROUP_CORE</group> -->
+                <group i18n="true">PLUGINS_CORE_DATASOURCE_CORE_CONFIG_JDBC_CATEGORY_DATASOURCE_GROUP_CORE</group>
             </ui-ref>
             
             <linked-params>
Index: main/plugin-core-impl/src/org/ametys/plugins/core/impl/checker/DirectoryChecker.java
===================================================================
--- main/plugin-core-impl/src/org/ametys/plugins/core/impl/checker/DirectoryChecker.java	(revision 32647)
+++ main/plugin-core-impl/src/org/ametys/plugins/core/impl/checker/DirectoryChecker.java	(working copy)
@@ -63,7 +63,7 @@
         
         if (configuration.getChild("linked-params").getChildren().length != 1)
         {
-            throw new ConfigurationException("The SqlConnectionChecker should have 1 linked param: directory");
+            throw new ConfigurationException("The Directory Checker should have exactly 1 linked parameter: directory");
         }
 
         _pathParameter = configuration.getChild("linked-params").getChild("param-ref").getAttribute("id");
Index: main/plugin-core-impl/src/org/ametys/plugins/core/impl/checker/SqlConnectionChecker.java
===================================================================
--- main/plugin-core-impl/src/org/ametys/plugins/core/impl/checker/SqlConnectionChecker.java	(revision 32647)
+++ main/plugin-core-impl/src/org/ametys/plugins/core/impl/checker/SqlConnectionChecker.java	(working copy)
@@ -45,7 +45,7 @@
         Configuration[] config = configuration.getChild("linked-params").getChildren();
         if (config.length != 4)
         {
-            throw new ConfigurationException("The SqlConnectionChecker should have 4 linked params in the right order: driver, url, user, password");
+            throw new ConfigurationException("The SqlConnectionChecker should have exactly 4 linked params in the following order: driver, url, user, password");
         }
         
         int i = 0;
@@ -64,7 +64,7 @@
         String login = configurationParameters.get(_paramUser);
         
         Connection connection = null;
-        try
+        try 
         {
             Class.forName(driver);
             connection = DriverManager.getConnection(url, login, password);
Index: main/plugin-core-ui/i18n/messages_en.xml
===================================================================
--- main/plugin-core-ui/i18n/messages_en.xml	(revision 32647)
+++ main/plugin-core-ui/i18n/messages_en.xml	(working copy)
@@ -203,10 +203,30 @@
     <message key="PLUGINS_CORE_UI_CONFIGURABLE_FORM_TEST_CONTROLLER_RESULTS_FAILURES">Failure(s)</message>
     <message key="PLUGINS_CORE_UI_CONFIGURABLE_FORM_TEST_CONTROLLER_RESULTS_NOT_TESTED">Not verified</message>
     
-    <!--  Password -->
-    <message key="PLUGINS_CORE_UI_PASSWORD_VALIDATOR">The password and the confirmation are not the same</message>
-    <message key="PLUGINS_CORE_UI_PASSWORD_CLEAR">Reset password to previous value</message>
+    <!-- SAVE -->
+    <message key="PLUGINS_CORE_UI_SAVE_NOFORM_TITLE">No form</message>
+    <message key="PLUGINS_CORE_UI_SAVE_NOFORM">There is no form to save.</message>
+    <message key="PLUGINS_CORE_UI_SAVE_ACTION_INVALID_TITLE">Invalid form</message>
+    <message key="PLUGINS_CORE_UI_SAVE_ACTION_INVALIDFIELDS">One or several fields are invalid.&lt;br/&gt;Please provide correct inputs: </message>
+    <message key="PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_TITLE">Test failure</message>
+    <message key="PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_MSG">Before saving, it is recommended that all verifications pass.&lt;br/&gt;What do you want to do ?</message>
+    <message key="PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_SAVE">Save without verifying</message>
+    <message key="PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_RETRY">Run the verifications and save</message>
+    <message key="PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_CANCEL">Cancel</message>
+    <message key="PLUGINS_CORE_UI_SAVE_WAIT_MSG">Saving. Please wait ...</message>
+    <message key="PLUGINS_CORE_UI_SAVE_ACTION_WARNED_FIELDS_YES">Yes</message>
+    <message key="PLUGINS_CORE_UI_SAVE_ACTION_WARNED_FIELDS_NO">No</message>
+    <message key="PLUGINS_CORE_UI_SAVE_ACTION_WARNED_FIELDS">Warnings</message>
+    <message key="PLUGINS_CORE_UI_SAVE_ACTION_WARNED_FIELDS_START">The following fields contain a warning: </message>
+    
+    <!-- Change password -->
+    <message key="PLUGINS_CORE_UI_CHANGEPASSWORD_VALIDATOR">The password and the confirmation are not the same</message>
+    <message key="PLUGINS_CORE_UI_CHANGEPASSWORD_CLEAR">Reset password to previous value</message>
+    <message key="PLUGINS_CORE_UI_CHANGEPASSWORD_CHANGE">Click here to change the password</message>
+    
+    <!-- Password -->
     <message key="PLUGINS_CORE_UI_PASSWORD_CHANGE">Click here to change the password</message>
+    <message key="PLUGINS_CORE_UI_PASSWORD_VALIDATE">Click here to validate the new password</message>
     
     <!-- Upload -->
     <message key="PLUGINS_CORE_UI_UPLOAD_HINT">Maximal authorized size is </message>
Index: main/plugin-core-ui/i18n/messages_fr.xml
===================================================================
--- main/plugin-core-ui/i18n/messages_fr.xml	(revision 32647)
+++ main/plugin-core-ui/i18n/messages_fr.xml	(working copy)
@@ -200,10 +200,31 @@
     <message key="PLUGINS_CORE_UI_CONFIGURABLE_FORM_TEST_CONTROLLER_RESULTS_FAILURES">Echec(s)</message>
     <message key="PLUGINS_CORE_UI_CONFIGURABLE_FORM_TEST_CONTROLLER_RESULTS_NOT_TESTED">Non testé(s)</message>
     
-    <!--  Password -->
+    <!-- Save -->
+    <message key="PLUGINS_CORE_UI_SAVE_NOFORM_TITLE">Pas de formulaire</message>
+    <message key="PLUGINS_CORE_UI_CONFIG_SAVE_NOFORM">Impossible de trouver le formulaire à sauvegarder.</message>
+    <message key="PLUGINS_CORE_UI_SAVE_ACTION_INVALID_TITLE">Formulaire invalide</message>
+    <message key="PLUGINS_CORE_UI_SAVE_ACTION_INVALIDFIELDS">Un ou plusieurs champs du formulaire sont invalides.&lt;br/&gt;Veuillez les corriger pour pouvoir continuer : </message>
+    <message key="PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_TITLE">Vérifications non-concluantes</message>
+    <message key="PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_MSG">Avant de sauver, il est recommandé que toutes les vérifications passent avec succès. &lt;br/&gt;Que souhaitez-vous faire ?</message>
+    <message key="PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_SAVE">Sauver sans vérifier les paramètres</message>
+    <message key="PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_RETRY">Lancer les vérifications et sauver</message>
+    <message key="PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_CANCEL">Annuler</message>
+    <message key="PLUGINS_CORE_UI_SAVE_WAIT_MSG">Sauvegarde en cours.&lt;br/&gt;Veuillez patienter ...</message>
+    <message key="PLUGINS_CORE_UI_SAVE_ACTION_WARNED_FIELDS_YES">Oui</message>
+    <message key="PLUGINS_CORE_UI_SAVE_ACTION_WARNED_FIELDS_NO">Non</message>
+    <message key="PLUGINS_CORE_UI_SAVE_ACTION_WARNED_FIELDS_TITLE">Alertes</message>
+    <message key="PLUGINS_CORE_UI_SAVE_ACTION_WARNED_FIELDS_START">Les champs suivants contiennent des alertes : </message>
+    <message key="PLUGINS_CORE_UI_SAVE_ACTION_WARNED_FIELDS_END">Souhaitez-vous quand même enregistrer le formulaire ?</message>
+    
+    <!--  Change password -->
+    <message key="PLUGINS_CORE_UI_CHANGEPASSWORD_CLEAR">Remettre le mot de passe à sa valeur initiale</message>
+    <message key="PLUGINS_CORE_UI_CHANGEPASSWORD_CHANGE">Cliquez ici pour changer le mot de passe</message>
     <message key="PLUGINS_CORE_UI_PASSWORD_VALIDATOR">Le mot de passe et sa confirmation ne sont pas identiques</message>
-    <message key="PLUGINS_CORE_UI_PASSWORD_CLEAR">Remettre le mot de passe à sa valeur initiale</message>
+    
+    <!-- Password -->
     <message key="PLUGINS_CORE_UI_PASSWORD_CHANGE">Cliquez ici pour changer le mot de passe</message>
+    <message key="PLUGINS_CORE_UI_PASSWORD_RESET">Cliquez ici pour remettre le mot de passe à sa valeur initiale</message>
     
     <!-- Upload -->
     <message key="PLUGINS_CORE_UI_UPLOAD_HINT">La taille maximale autorisée est de </message>
@@ -463,7 +484,7 @@
     <!-- +
          | SaveAction.js
          + -->
-    <message key="PLUGINS_CORE_UI_SAVE_ACTION_FAILED_TITLE">Save failed</message>
+    <message key="PLUGINS_CORE_UI_SAVE_ACTION_FAILED_TITLE">Echec de la sauvegarde</message>
     <message key="PLUGINS_CORE_UI_SAVE_ACTION_FAILED_OK_BTN">Ok</message>
     
     <!-- +
Index: main/plugin-core-ui/plugin.xml
===================================================================
--- main/plugin-core-ui/plugin.xml	(revision 32647)
+++ main/plugin-core-ui/plugin.xml	(working copy)
@@ -109,11 +109,13 @@
                     <content>edition.select-content</content>
                     <date>edition.date</date>
                     <datetime>edition.datetime</datetime>
+                    <stringtime>edition.string-time</stringtime>
                     <double>edition.doublefield</double>
                     <file>edition.file</file>
                     <geocode>edition.geocode</geocode>
                     <long>edition.longfield</long>
-                    <password>edition.changepassword</password>
+                    <password>edition.password</password>
+                    <change_password>edition.changepassword</change_password>
                     <reference>edition.url-reference</reference>
                     <rich_text>edition.richtext</rich_text>
                     <string>edition.textfield</string>
@@ -203,7 +205,7 @@
                     <file>js/Ametys/helper/ChooseLocation.js</file>
                     <file>js/Ametys/helper/ChooseLocation/LoadGoogleMaps.js</file>
 
-                    <file>js/Ametys/helper/SaveErrorDialog.js</file>
+                    <file>js/Ametys/form/SaveErrorDialog.js</file>
                     
                     <file>js/Ametys/clipboard/Clipboard.js</file>
                 </scripts>
@@ -247,21 +249,33 @@
             </extension>
         </extensions>
     </feature>
+    <feature name="widgets.default.password">
+        <extensions>
+            <extension id="edition.password"
+                point="org.ametys.core.ui.widgets.WidgetsManager"
+                class="org.ametys.core.ui.widgets.StaticClientSideWidget">
+                <class name="Ametys.form.field.Password">
+                    <ftypes>password</ftypes>
+                    <supports-enumerated>false</supports-enumerated>
+                    <supports-non-enumerated>true</supports-non-enumerated>
+                    <supports-multiple>false</supports-multiple>
+                    <supports-non-multiple>true</supports-non-multiple>
+                </class>
+            </extension>
+        </extensions>
+    </feature>
     <feature name="widgets.default.changepassword">
         <extensions>
             <extension id="edition.changepassword"
                 point="org.ametys.core.ui.widgets.WidgetsManager"
                 class="org.ametys.core.ui.widgets.StaticClientSideWidget">
                 <class name="Ametys.form.field.ChangePassword">
-                    <ftypes>password</ftypes>
+                    <ftypes>changepassword</ftypes>
                     <supports-enumerated>false</supports-enumerated>
                     <supports-non-enumerated>true</supports-non-enumerated>
                     <supports-multiple>false</supports-multiple>
                     <supports-non-multiple>true</supports-non-multiple>
                 </class>
-                <scripts>
-                    <file>js/Ametys/form/widget/DefaultWidgets.js</file>
-                </scripts>                
             </extension>
         </extensions>
     </feature>
@@ -391,23 +405,23 @@
             </extension>
         </extensions>
     </feature>    
-<!--     <feature name="widgets.others.stringhour"> -->
-<!--         <extensions> -->
-<!--             <extension id="edition.string-time" -->
-<!--                 point="org.ametys.core.ui.widgets.WidgetsManager" -->
-<!--                 class="org.ametys.core.ui.widgets.StaticClientSideWidget"> -->
-<!--                 <class name="Ametys.form.widget.StringTime"> -->
-<!--                     <ftypes>string</ftypes> -->
-<!--                     <supports-enumerated>false</supports-enumerated> -->
-<!--                     <supports-non-enumerated>true</supports-non-enumerated> -->
-<!--                     <supports-multiple>false</supports-multiple> -->
-<!--                     <supports-non-multiple>true</supports-non-multiple> -->
-<!--                 </class> -->
-<!--                 <scripts> -->
-<!--                 </scripts>                 -->
-<!--             </extension> -->
-<!--         </extensions> -->
-<!--     </feature>     -->
+    
+    <feature name="widgets.others.stringhour"> 
+        <extensions> 
+            <extension id="edition.string-time" 
+                point="org.ametys.core.ui.widgets.WidgetsManager" 
+                class="org.ametys.core.ui.widgets.StaticClientSideWidget"> 
+                <class name="Ametys.form.widget.StringTime"> 
+                    <ftypes>string</ftypes> 
+                    <supports-enumerated>false</supports-enumerated> 
+                    <supports-non-enumerated>true</supports-non-enumerated> 
+                    <supports-multiple>false</supports-multiple> 
+                    <supports-non-multiple>true</supports-non-multiple> 
+                </class> 
+                <!-- scripts are loaded as static to be extensible -->                 
+            </extension>
+        </extensions>
+    </feature>     
     
     <feature name="widgets.default.richtext">
         <extensions>
Index: main/plugin-core-ui/resources/css/Ametys/ui/form/common/ConfigurableFormPanel.css
===================================================================
--- main/plugin-core-ui/resources/css/Ametys/ui/form/common/ConfigurableFormPanel.css	(revision 32647)
+++ main/plugin-core-ui/resources/css/Ametys/ui/form/common/ConfigurableFormPanel.css	(working copy)
@@ -344,81 +344,50 @@
 
 .ametys-form-panel .param-checker-container
 {
-    
-}
-
-.ametys-form-panel .param-checker-container .x-box-inner
-{
-    width: 262px !important;
-    position: static;
+    margin-top: 10px;
+    margin-bottom: 10px;
 }
 
-.ametys-form-panel .param-checker-component
+.ametys-form-panel .param-checker-button
 {
     border: 1px solid #b9b9b9 !important;
     background-color: #e3e3e3;
 }
 
-.ametys-form-panel .param-checker-component .x-mask-msg 
+.ametys-form-panel .param-checker-button .x-mask-msg 
 {
     background-color: transparent !important;
     border: 1px !important;
 }
 
-.ametys-form-panel .param-checker-component .x-mask-msg div 
+.ametys-form-panel .param-checker-button .x-mask-msg div 
 {
     background-color: transparent !important;
 }
 
-.ametys-form-panel .param-checker-component.failure
+.ametys-form-panel .param-checker-container .param-checker-status.failure,
+.ametys-form-panel .param-checker-container .param-checker-status.success,
+.ametys-form-panel .param-checker-container .param-checker-status.warning
 {
-    width: 220px;
-}
- 
-.ametys-form-panel .param-checker-component.success
-{
-    width: 220px;   
+    display: inline-block;
+    margin-top: 2px !important;
+    margin-left: 3px !important;
+    background-repeat: no-repeat;
 }
 
-.ametys-form-panel .param-checker-component.warning
+.ametys-form-panel .param-checker-container .param-checker-status.failure
 {
-    width: 220px;   
+    background-image: url('../../../../../../../../plugins/extjs6/resources/classic/theme-gray/resources/images/form/exclamation.gif');
 }
  
-.ametys-form-panel .param-checker-component.failure:after
+.ametys-form-panel .param-checker-container .param-checker-status.success
 {
-    content: ' ';
-    display: inline-block;
-    background: url('../../../../../../../../plugins/extjs6/resources/classic/theme-gray/resources/images/form/exclamation.gif') left no-repeat;
-    height: 20px; 
-    width: 20px;
-    position: absolute;
-    bottom: -1px;
-    right: -27px; 
-}
- 
-.ametys-form-panel .param-checker-component.success:after
-{ 
-    content: ' ';
-    display: inline-block;
-    background: url('../../../../../resources/img/Ametys/theme/gray/info.png') no-repeat;
-    height: 20px;
-    width: 20px;
-    position: absolute;
-    bottom: -1px;
-    right: -27px; 
+    background-image: url('../../../../../img/Ametys/common/form/configurable/tests/testall_16.png');
 }
 
-.ametys-form-panel .param-checker-component.warning:after
-{ 
-    content: ' ';
-    display: inline-block;
-    background: url('../../../../../img/Ametys/theme/gray/warning.png') no-repeat;
-    height: 20px;
-    width: 20px;
-    position: absolute;
-    bottom: -1px;
-    right: -27px; 
+.ametys-form-panel .param-checker-container .param-checker-status.warning
+{
+    background-image: url('../../../../../img/Ametys/theme/gray/warning.png');
 }
  
 .ametys-form-panel .test-results
@@ -427,20 +396,6 @@
    font-weight: normal; 
    font-family: arial,sans-serif;
 }
-
-.ametys-form-panel .offset
-{
-    margin-left: 25px !important;
-}
  
-.ametys-form-panel .param-checker-component.x-btn-over
-{
-   background-position: -16px 0;
-}
-
-.ametys-form-panel .param-checker-component.x-btn-pressed
-{
-   background-position: -32px 0;
-}*/
 /** @end Parameter checkers */
 
Index: main/plugin-core-ui/resources/js/Ametys/form/AbstractFieldsWrapper.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/form/AbstractFieldsWrapper.js	(revision 32647)
+++ main/plugin-core-ui/resources/js/Ametys/form/AbstractFieldsWrapper.js	(working copy)
@@ -15,7 +15,7 @@
  */
 
 /**
- * Abstract class to creates a field that wraps several fields.<br/>
+ * Abstract class to create a field that wraps several fields.<br/>
  * You have to implement {@link #getValue} and {@link #setValue}.<br/>
  * Default layout is hbox.<br/>
  * To handle global errors override {@link #getErrors}.<br/>
Index: main/plugin-core-ui/resources/js/Ametys/form/ConfigurableFormPanel.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/form/ConfigurableFormPanel.js	(revision 32647)
+++ main/plugin-core-ui/resources/js/Ametys/form/ConfigurableFormPanel.js	(working copy)
@@ -57,6 +57,14 @@
          * @readonly 
          */
         LABEL_WIDTH: 200,
+        
+        /**
+         * @property {Number} LABEL_PADDING The padding for field labels 
+         * @private
+         * @readonly 
+         */
+        LABEL_PADDING: 5,
+        
         /**
          * @property {Number} FIELD_MINWIDTH The minimum width for fields
          * @private
@@ -138,11 +146,6 @@
      */
     
     /**
-     * @property {Object} _switchers The group switchers are a map of fields that switches their group. A mapping between a field name and the Ext.form.Field value.
-     * @private
-     */
-    
-    /**
      * @property {Ametys.form.ConfigurableFormPanel.ParameterCheckersDAO} _paramCheckersDAO The istance of the parameters checkers DAO of this form
      * @private
      */
@@ -223,7 +226,6 @@
         this._fields = [];  
         this._repeaters = [];
         this._tabPanels = [];
-        this._switchers = {};
 
         this._paramCheckersDAO = Ext.create('Ametys.form.ConfigurableFormPanel.ParameterCheckersDAO', {
             form: this
@@ -339,31 +341,6 @@
     },
     
     /**
-     * Get a field in this form by id or name
-     * @param {String} name The name (or id) of the searched field
-     * @return {Ext.form.field.Field} The first matching field or switcher, or null if none was found.
-     */
-    getField: function (name)
-    {
-        return this._switchers.hasOwnProperty(name) ? this._switchers[name] : this.getForm().findField(name);
-    },
-    
-    /**
-     * Get all the fields of the form, including the switchers
-     * @return {Ext.form.field.Field[]} An array of fields
-     */
-    getFields: function ()
-    {
-        var fields = this.getForm().getFields();
-        
-        Ext.Object.each(this._switchers, function(fieldName) {
-            fields.push(this[fieldName]);
-        });
-    
-        return fields;  
-    },
-    
-    /**
      * Call Ext.form.field.Field.markInvalid on all fields of the form that are in error
      * @param {Object} fieldsInError The fields in error: the key is the name and the value is the error message.
      */
@@ -371,7 +348,7 @@
     {
         for (var name in fieldsInError)
         {
-            var fd = this.getField (name);
+            var fd = this.getForm().findField(name);
             if (fd)
             {
                 fd.markInvalid (fieldsInError[name])
@@ -678,11 +655,11 @@
     {
         var panelContainers = panel.query('container'),
             paramCheckers = [];
-        
+
         Ext.Array.each(panelContainers, function(container){
             if (container.hasCls('param-checker-container'))
             {
-                paramCheckers.push(container.down().paramChecker);
+                paramCheckers.push(container.down('button').paramChecker);
             }
         });
         
@@ -835,37 +812,33 @@
             header[startup ? 'addCls' : 'removeCls']('startup');
             header[startup ? 'removeCls' : 'addCls']('not-startup');
             
-            var hasError = errorFields.length > 0;
-            var hasWarn = warnFields.length > 0;
+            var errors = Ext.Array.union(errorFields, testsErrorMessages);
+            var warnings = Ext.Array.union(warnFields, testsWarnMessages);
+            
+            var hasError = errors.length > 0;
+            var hasWarn = warnings.length > 0;
             var hasComment = commentFields.length > 0;
             
-            var headerCls = '';
+            header.removeCls(['error', 'warning', 'comment']);
             if (hasError)
             {
-                header.removeCls(['warning']);
-                header.removeCls(['comment']);
-                header.addCls('error')
+                header.addCls('error');
             }
             else if (hasWarn)
             {
-                header.removeCls(['error']);
-                header.removeCls(['comment']);
-                header.addCls('warning')
+                header.addCls('warning');
             }
             else if (hasComment)
             {
-                header.removeCls(['error']);
-                header.removeCls(['warning']);
-                header.addCls('comment')
-            }
-            else
-            {
-                header.removeCls(['error', 'warning', 'comment']);
+                header.addCls('comment');
             }
             
             if (header.rendered)
             {
-                this._createTabTooltip (header, panel, errorFields, warnFields, commentFields);
+            	// As we change width with CSS we have to prevent tabs from overlapping one another
+            	header.updateLayout();
+            	
+                this._createTabTooltip (header, panel, errors, warnings, commentFields);
             }
             else
             {
@@ -1046,6 +1019,12 @@
             {
                 header.removeCls(['empty']);
             }
+            
+            var focusedTool = Ametys.tool.ToolsManager.getFocusedTool();
+            if (focusedTool != null && this._formReady && !focusedTool.isDirty())
+        	{
+            	focusedTool.setDirty(true);
+        	}	
         }
     },
     
@@ -1053,7 +1032,7 @@
      * @private
      * Show or hide the elements of a fieldset, including the parameter checkers
      * @param {Ext.form.field.Checkbox} checkbox the checkbox 
-     * @param {Ext.panel.Panel} fieldset the fieldset the group switchs belongs to
+     * @param {Ext.panel.Panel} fieldset the fieldset the group switch belongs to
      * @param {Boolean} startup true if this is the first call, false otherwise
      */
     _showHideFieldset: function(checkbox, fieldset, startup)
@@ -1061,21 +1040,30 @@
         Ext.suspendLayouts();
         
         var checked = checkbox.getValue();
+
         fieldset.items.eachKey(function(key){
-            var fieldsetElement = Ext.getCmp(key);
-            fieldsetElement.setVisible(checked);
-            
+        	
+        	var fieldsetElement = Ext.getCmp(key);
+        	if (fieldsetElement.getId() == checkbox.getId())
+    		{
+        		return;
+    		}
+
+        	fieldsetElement.setVisible(checked);
+        	
             if (fieldsetElement.hasCls('param-checker-container'))
             {
                 // parameter checker
-                fieldsetElement.down().paramChecker.setStatus(checked ? Ametys.form.ConfigurableFormPanel.ParameterChecker.STATUS_NOT_TESTED
+                fieldsetElement.down('button').paramChecker.setStatus(checked ? Ametys.form.ConfigurableFormPanel.ParameterChecker.STATUS_NOT_TESTED
                                                                       : Ametys.form.ConfigurableFormPanel.ParameterChecker.STATUS_HIDDEN);
             }
-            else if (!startup)
-            {
-                // validate fields on expanding
+        	else if (!startup)
+    		{
+        		// validate fields on expanding/collapsing
+        		// validation on hidden fields always returns true
                 fieldsetElement.validate();
-            }
+    		}
+            
         });
 
         if (checked)
@@ -1100,6 +1088,7 @@
     _preventClickPropagation: function(checkbox)
     {
         checkbox.getEl().on('click', function(event) {event.stopPropagation();})
+        // checkbox.show();
     },
     
     /**
@@ -1108,11 +1097,10 @@
      * @param {String} label The label of the fieldset
      * @param {Number} nestingLevel The nesting level of the fieldset.
      * @param {Object} switcher If the group can be switched on/off, the configuration object corresponding to the group-switch parameter. A config for #_createInputField.
-     * @param {String[]} fieldNames An array containing the names of the fields the switcher will show/hide 
      * @return {Ext.form.FieldSet} The created fieldset
      * @private
      */
-    _addFieldSet: function (ct, label, nestingLevel, switcher, fieldNames)
+    _addFieldSet: function (ct, label, nestingLevel, switcher)
     {
         var me = this;
         var fdCfg = {
@@ -1133,13 +1121,8 @@
             var switcherCfg  = Ext.apply ({minWidth: 20, style: ' '}, switcher);
             
             var switcherField = this._createInputField(switcherCfg);
-            
             switcherField.on('render', Ext.bind(this._preventClickPropagation, this));
             
-            // Register the switcher as a field
-            this._fields.push(switcherCfg.name);
-            this._switchers[switcherCfg.name] = switcherField;
-
             Ext.apply(fdCfg, {
                 title: switcherCfg.label,
                 
@@ -1148,6 +1131,8 @@
                 hideCollapseTool: true,
                 cls: 'ametys-fieldset',             
                 
+                tools: [switcherField],
+                
                 header: {
                     titlePosition: 1,
                     
@@ -1157,7 +1142,7 @@
                                 if (!switcherField.checked) 
                                 {
                                     // When the group is switched off, we cannot collapse/expand it
-                                    event.stopEvent();
+                                	event.stopEvent();
                                     return false;
                                 }
                                 else
@@ -1167,22 +1152,17 @@
                             }
                         } 
                     }
-            
                 },
                 
                 listeners: {
                     add: {
                         fn: function(){
                             // every time something is added to this panel we want to hide it depending on the switch value
-                            // hiding such element will make isValid works as we want
+                            // hiding such element will make isValid work as we want
                             me._showHideFieldset(switcherField, this, true);
                         }
                     }
-                },
-                
-                tools: [
-                    switcherField
-                ]
+                }
             });
         }
         else if (label)
@@ -1198,12 +1178,18 @@
         }
         
         var fieldset = new Ext.panel.Panel(fdCfg);
-
+        
+        // we need to add the switchfield here as a regular field in order for the form to consider it as a field immediately
+        // otherwise tools of the header are only added during rendering
+        // The field will be moved from the fieldset to the head automatically since a component cannot be used twice
+        fieldset.add(switcherField);
+        
         if (switcherField != null)
         {
             switcherField.on('change', Ext.bind(this._showHideFieldset, this, [fieldset, false], 1));
         }
         ct.add(fieldset);
+        
         return fieldset;
     },
     
@@ -1232,7 +1218,7 @@
         var field = this._createInputField(config);
         if (startVisible == 'false')
         {
-            field.hide();
+           field.hide();
         }
         
         if (field != null)
@@ -1280,9 +1266,12 @@
         Ext.applyIf (fieldCfg, {
             cls: 'ametys',
             style: config.style || 'margin-right:' + Math.max(60 - roffset, 0) + 'px',
+            
             labelAlign: 'right',
             labelWidth: Ametys.form.ConfigurableFormPanel.LABEL_WIDTH - offset,
+            labelPad: Ametys.form.ConfigurableFormPanel.LABEL_PADDING,
             labelSeparator: '',
+            
             minWidth: config.minWidth || Ametys.form.ConfigurableFormPanel.LABEL_WIDTH - offset + Ametys.form.ConfigurableFormPanel.FIELD_MINWIDTH,
             anchor: '100%',
             
@@ -1511,7 +1500,7 @@
      * - String **role** Can be "tabs" or "fieldsets" to create a tab grouping or a fieldset grouping. Note that tab grouping can be replaced by simple panels according to a user preference.
      * - String **label** The label of the grouping.
      * - Object **switcher** The configuration of the switcher parameter. The switcher is a boolean field that is used to enable/disable the other fields of its fieldset (available for fieldsets that have a "fieldset" role exclusively).
-     *   It must have a **label** and an **id** (the id of the boolean field) and optionally a **defaultValue**. This configuration will be ignored if there is a **paramChecker** defined for the fieldset.
+     *   It must have a **label** and an **id** (the id of the boolean field) and optionally a **defaultValue**.
      * - Object **param-checker** (see above) Fieldsets can also have a parameter checker for both "tab" and "fieldset" role
      * - Object **elements** The child elements of the grouping : this is a recursive data object, except that **"fieldsets"** can not be used again with the role "tab".
      * 
@@ -1572,7 +1561,7 @@
                     var fieldsets = data[name];
                     for (var i=0; i < fieldsets.length; i++)
                     {
-                        if (role == 'fieldset')
+                        if (fieldsets[i].role == 'fieldset')
                         {
                             var elements = fieldsets[i].elements;
                             
@@ -1582,15 +1571,9 @@
                                 switcherCfg.name = switcherCfg.id;
                                 delete switcherCfg.id;
                                 switcherCfg.type = 'boolean';
-                                
-                                var fieldNames = [];
-                                for (var fieldId in elements)
-                                {
-                                    fieldNames.push(fieldId);
-                                }
                             }
                             
-                            var fieldset = this._addFieldSet(ct, fieldsets[i].label, nestingLevel, switcherCfg, fieldNames);
+                            var fieldset = this._addFieldSet(ct, fieldsets[i].label, nestingLevel, switcherCfg);
                             
                             // Transmit offset + 5 (padding) + 1 (border) + 11 (margin + border) if we are in a nested composite.
                             var finalOffset = offset 
@@ -1612,7 +1595,7 @@
                                 }
                                 
                                 Ext.Array.each(paramCheckers, function(paramChecker){
-                                    me._paramCheckersDAO.addGroupChecker(fieldset, paramChecker);
+                                    me._paramCheckersDAO.addGroupChecker(fieldset, paramChecker, finalOffset, finalROffset);
                                 });
                             }
                         }
@@ -1667,11 +1650,11 @@
                 }
                 else
                 {
-                    var metadata = data[name];
+                    var fieldData = data[name];
                     
-                    var label = metadata.label;
-                    var description = metadata.description;
-                    var isMandatory = metadata.validation ? (metadata.validation.mandatory) || false : false;
+                    var label = fieldData.label;
+                    var description = fieldData.description;
+                    var isMandatory = fieldData.validation ? (fieldData.validation.mandatory) || false : false;
                     
                     var widgetCfg = {
                         name: prefix + name,
@@ -1683,13 +1666,15 @@
                         showAmetysComments: this.showAmetysComments,
                         
                         mandatory: isMandatory,
-                        value: metadata['default-value'],
+                        value: fieldData['default-value'],
                         
-                        multiple: metadata.multiple,
-                        widget: metadata.widget,
+                        multiple: fieldData.multiple,
+                        widget: fieldData.widget,
                         
-                        hidden: metadata.hidden, 
-                        disabled: metadata['can-not-write'] === true,
+                        hidden: fieldData.hidden, 
+                        disabled: fieldData['can-not-write'] === true,
+                        
+                        disableCondition: fieldData.disableCondition,
                         
                         form: this,
                         offset: offset,
@@ -1698,12 +1683,12 @@
 
                     // Add configured configuration
                     Ext.Object.each(this._additionnalWidgetsConf, function(key, value, object) {
-                        widgetCfg[key] = metadata[value];
+                        widgetCfg[key] = fieldData[value];
                     });
                     
-                    if (metadata.validation)
+                    if (fieldData.validation)
                     {
-                        var validation = metadata.validation;
+                        var validation = fieldData.validation;
                         widgetCfg.regexp = validation.regexp || null;
                         
                         if (validation.invalidText)
@@ -1716,11 +1701,11 @@
                         }
                     }
                     
-                    if (metadata.enumeration)
+                    if (fieldData.enumeration)
                     {
                         var enumeration = [];
                         
-                        var entries = metadata.enumeration;
+                        var entries = fieldData.enumeration;
                         for (var j=0; j < entries.length; j++)
                         {
                             enumeration.push([entries[j].value, entries[j].label]);
@@ -1729,19 +1714,19 @@
                         widgetCfg.enumeration = enumeration;
                     }
                     
-                    if (metadata['widget-params'])
+                    if (fieldData['widget-params'])
                     {
-                        widgetCfg = Ext.merge (widgetCfg, metadata['widget-params']);
+                        widgetCfg = Ext.merge (widgetCfg, fieldData['widget-params']);
                     }
                     
-                    if (metadata.annotations)
+                    if (fieldData.annotations)
                     {
                         var annotations = [];
                         
-                        var entries = metadata.annotations;
+                        var entries = fieldData.annotations;
                         for (var j=0; j < entries.length; j++)
                         {
-                            annotations.push({
+                        	fieldData.push({
                                 name: entries[j].name,
                                 label: entries[j].label || entries[j].name,
                                 description: entries[j].description || entries[j].label || entries[j].name
@@ -1753,7 +1738,7 @@
                     
                     this._addInputField(ct, widgetCfg);
                     
-                    var paramCheckers = metadata['param-checker'];
+                    var paramCheckers = fieldData['param-checker'];
                     if (!Ext.isEmpty(paramCheckers))
                     {
                         if (paramCheckers.length > 1)
@@ -1762,7 +1747,7 @@
                         }
                         
                         Ext.Array.each(paramCheckers, function(paramChecker){
-                            me._paramCheckersDAO.addGroupChecker(fieldset, paramChecker);
+                            me._paramCheckersDAO.addParameterChecker(fieldset, paramChecker, offset, roffset);
                         });
                     }
                 }
@@ -1786,7 +1771,7 @@
                     this._configureJSON (tabs[i].elements, prefix, tab, finalOffset, finalROffset);
                 
                     // Add the parameter checker at the end of the current tab
-                    var paramCheckers = metadata['param-checker']
+                    var paramCheckers = tabs[i]['param-checker'];
                     if (!Ext.isEmpty(paramCheckers))
                     {
                         if (paramCheckers.length > 1)
@@ -1794,7 +1779,7 @@
                             paramCheckers.sort(me._compareByOrder);
                         }
                         Ext.Array.each(paramCheckers, function(paramChecker) {
-                            me._paramCheckersDAO.addCategoryChecker(tab, paramChecker);
+                            me._paramCheckersDAO.addCategoryChecker(tab, paramChecker, finalOffset, finalROffset);
                         });
                     }
                 }
@@ -1810,7 +1795,7 @@
      * @param {Number} [offset=0] The field offset.
      * @param {Number} [roffset=0] The field right offset.
      */
-    _configureXML: function (metadataNode, prefix, ct, offset, roffset)
+    _configureXML: function (data, prefix, ct, offset, roffset)
     {
             prefix = prefix || this.getFieldNamePrefix();
             ct = ct || this._getFormContainer();
@@ -1819,7 +1804,7 @@
             
             var me = this;
             
-            var nodes = Ext.dom.Query.selectDirectElements(null, metadataNode);
+            var nodes = Ext.dom.Query.selectDirectElements(null, data);
             
             var tabs = [];
             
@@ -1866,20 +1851,9 @@
                             switcherCfg.label = Ext.dom.Query.selectValue('> label', switcher);
                             switcherCfg.defaultValue = Ext.dom.Query.selectValue('> defaultValue', switcher);
                             switcherCfg.type = 'boolean';
-                            
-                            // Gather the names of the other fields into an array
-                            var fieldNames = [];
-                            for (var j=0; j < elements.childNodes.length; j++)
-                            {
-                                var elementId = elements.childNodes[j].tagName;
-                                if (elementId != switcherCfg.id)
-                                {
-                                    fieldNames.push(elementId);
-                                }
-                            }
                         }
                         
-                        var fieldset = this._addFieldSet(ct, label, nestingLevel, switcher != null ? switcherCfg : null, fieldNames);
+                        var fieldset = this._addFieldSet(ct, label, nestingLevel, switcher != null ? switcherCfg : null);
 
                         // Transmit offset + 5 (padding) + 1 (border) + 11 (margin + border) if we are in a nested composite.
                         var finalOffset = offset 
@@ -1912,7 +1886,7 @@
                                     'order':  Ext.dom.Query.selectValue("> order", paramChecker)
                                 };
                                 
-                                me._paramCheckersDAO.addGroupChecker(fieldset, paramCheckerConf);
+                                me._paramCheckersDAO.addGroupChecker(fieldset, paramCheckerConf, finalOffset, finalROffset);
                             });
                         }
                     }
@@ -1969,7 +1943,6 @@
                     
                     var widgetCfg = {
                         name: prefix + name,
-                        metadataName: name,
                         type: Ext.dom.Query.selectValue("> type", nodes[i]),
                         
                         fieldLabel: (isMandatory ? '* ' : '') + label,
@@ -1988,8 +1961,7 @@
                         // custom validator to bypass validation on hidden values (groups switched off)
                         validator: function(value)
                         {   
-                            var field = me.getField(name);
-                            if(!field.isVisible() || field.isDisabled())
+                            if(!this.isVisible() || this.isDisabled())
                             {
                                 return true;
                             }
@@ -2084,7 +2056,7 @@
                                 'order':  Ext.dom.Query.selectValue("> order", paramChecker)
                             };
                             
-                            me._paramCheckersDAO.addParameterChecker(paramCheckerConf);
+                            me._paramCheckersDAO.addParameterChecker(paramCheckerConf, offset, roffset);
                         })
                     }
                 }
@@ -2133,7 +2105,7 @@
                                 'order':  Ext.dom.Query.selectValue("> order", paramChecker)
                             };
                             
-                            me._paramCheckersDAO.addCategoryChecker(tab, paramCheckerConf);
+                            me._paramCheckersDAO.addCategoryChecker(tab, paramCheckerConf, finalOffset, finalROffset);
                         });
                     }
                 }
@@ -2156,7 +2128,7 @@
     },
 
     /**
-     * Fill a configured form with values. #configure must have been call previously with data corresponding to these data.
+     * Fill the configured form with values. #configure must have been called previously with data matching the configured data.
      * 
      * The data can be an XML or a JSON object.
      * 
@@ -2166,24 +2138,24 @@
      * 
      * 
      *      <myrootnode>
-     *          <metadata>
+     *          <values>
      *              <fieldname json="false" value="3"/>
      *              <!-- ... -->
-     *          </metadata>
+     *          </values>
      *          
      *          <comments>
-     *              <metadata path="fieldname">
+     *              <field path="fieldname">
      *                  <comment id="1" date="2020-12-31T23:59:59.999+02:00">
      *                      My comment for the field <fieldname>
      *                  </comment>
      *                  <!-- ... -->
-     *              </metadata>
+     *              </field>
      *              <!-- ... -->
      *          </comments>
      *      </myrootnode>
      * 
      * 
-     * For the values, the tag **metadata** is the wrapping for tags holding the values:
+     * For the values, the tag **metadata** is the wrapping for tags holding the values and its name is configurable (see **valuesTagName** parameter):
      * 
      * - the tag name is the name of the concerned field (without prefix).
      * - thoses tags are recursive for sub-field (child of composites).
@@ -2192,15 +2164,15 @@
      * - the value itself can be either the value of the attribute **value**, or the text of the tag
      * - multiple values are set by repeating the tag.
      *   
-     * For the **comments**:
+     * For the comments, the tag **comments** is the wrapping tag and its name is configurable (see **commentsTagName** parameter):
      * 
-     * - the **metadata** are not recursive
+     * - the **metadata** are not recursive 
      * - the **path** attribute contains the fieldname (without prefix) with '/' separator for sub-fields (child of composites). For repeaters, you also have to add the position of the repeater to modify. Exemple: path="mycompositefield/myrepeater/2/myfield"
      * - the **comment** tag have the following mandatory attributes :
      *   - **id** The number of the comment
      *   - **date** The date of the comment using the ISO 8601 format (will use the Ext.Date.patterns.ISO8601DateTime parser).
      *   - **author** The fullname of the author of the comment.
-     *   
+     *  
      *  Here is a full example:
      *  
      *  
@@ -2295,16 +2267,23 @@
      * The **invalid** values should not set the same values already brought by **values**, but they will replace them in such a case.
      * 
      * @param {Object/HTMLElement} data The object that will fill the form.
+     * @param {String} valuesTagName the tag name for the values 
+     * @param {String} commentsTagName the tag name for the comments 
+     * @param {String} invalidFieldsTagName the tag name for the invalid fields
      */
-    setValues: function(data)
+    setValues: function(data, valuesTagName, commentsTagName, invalidFieldsTagName)
     {
-        if (this._isElement(data))
+    	valuesTagName = valuesTagName || "values";
+    	commentsTagName = commentsTagName || "comments";
+    	invalidFieldsTagName = invalidFieldsTagName || "invalid";
+    	
+    	if (this._isElement(data))
         {
-            this._setValuesXML(data);
+            this._setValuesXML(data, valuesTagName, commentsTagName);
         }
         else
         {
-            this._setValuesJSON(data);
+            this._setValuesJSON(data, valuesTagName, commentsTagName, invalidFieldsTagName);
         }
         
         this._formReady = true;
@@ -2316,30 +2295,33 @@
     /**
      * This function set the values of form fields from a XML dom.
      * @param {HTMLElement} xml The XML dom
+     * @param {String} valuesTagName the tag name for the values 
+     * @param {String} commentsTagName the tag name for the comments 
      * @private
      */
-    _setValuesXML: function (xml)
+    _setValuesXML: function (xml, valuesTagName, commentsTagName)
     {
         this._initializeTabsStatus();
         
-        var metadataNodes = Ext.dom.Query.select("metadata/*", xml);
+        var metadataNodes = Ext.dom.Query.select(valuesTagName + "/*", xml);
         for (var i=0; i < metadataNodes.length; i++)
         {
             this._setValuesXMLMetadata(metadataNodes[i], this.getFieldNamePrefix() + metadataNodes[i].tagName);
         }
         
-        this._setValuesXMLComments(Ext.dom.Query.selectNode("comments", xml));
+        this._setValuesXMLComments(Ext.dom.Query.selectNode(commentsTagName, xml));
     },
     
     /**
      * Set the values of form fields from an automatic backup.
      * @param {Object} data The backup data object.
      * @param {Object[]} data.repeaters The repeater item counts.
-     * @param {Object} data.values The valid field values.
-     * @param {Object} data.invalid The invalid field values.
+     * @param {String} valuesTagName the tag name for the values 
+     * @param {String} commentsTagName the tag name for the comments 
+     * @param {String} invalidFieldsTagName the tag name for the invalid fields
      * @private
      */
-    _setValuesJSON: function(data)
+    _setValuesJSON: function(data, valuesTagName, commentsTagName, invalidFieldsTagName)
     {
         this._initializeTabsStatus();
         
@@ -2387,13 +2369,13 @@
         }
         
         // Set the field values.
-        this._setValuesJSONForField(data.values);
+        this._setValuesJSONForField(data[valuesTagName]);
         
         // Set the invalid field values (raw mode) and validate the fields afterwards.
-        this._setValuesJSONForField(data.invalid, true, true);
+        this._setValuesJSONForField(data[invalidFieldsTagName], true, true);
         
         // Set the field comments.
-        this._setMetadataCommentsJSON(data.comments)
+        this._setMetadataCommentsJSON(data[commentsTagName])
     },
     
     /**
@@ -2423,7 +2405,7 @@
                 // FIXME this do logs an error now!
             }
             
-            var field = this.getField(this.getFieldNamePrefix() + name);
+            var field = this.getForm().findField(this.getFieldNamePrefix() + name);
             if (field != null)
             {
                 if (rawMode && field.setRawValue)
@@ -2510,7 +2492,7 @@
             {
                 var values = this._getValues (metadataNode);
                 
-                var field = this.getField(fieldName);
+                var field = this.getForm().findField(fieldName);
                 if (field != null)
                 {
                     if (metadataNode.getAttribute('json') == 'true')
@@ -2569,7 +2551,7 @@
     {
         var invalidValues = {};
         
-        var fields = this.getFields().items;
+        var fields = this.getForm().getFields().items;
         for (var i = 0; i < fields.length; i++)
         {
             if (!fields[i].isHidden() && fields[i].getErrors().length > 0)
@@ -2624,7 +2606,7 @@
         var invalidFields = [];
         for (var i = 0; i < this._fields.length; i++)
         {
-            var fd = this.getField(this._fields[i]);
+            var fd = this.getForm().findField(this._fields[i]);
             if (!fd.isValid())
             {
                 invalidFields.push(fd.getFieldLabel());
@@ -2641,22 +2623,22 @@
     
     /**
      * Get the fields with warnings
-     * @return {String[]} The names of the fields with warnings
+     * @return {Object} The mapping of warned field names with their warning message
      */
     getWarnedFields: function()
     {
-        var invalidFields = [];
+        var warnedFields = {};
         for (var i = 0; i < this._fields.length; i++)
         {
-            var fd = this.getField(this._fields[i]);
-            if (fd.getActiveWarning() != null)
+            var fd = this.getForm().findField(this._fields[i]);
+            if (!Ext.isEmpty(fd.getActiveWarning()))
             {
-                invalidFields.push(fd.getFieldLabel());
+            	warnedFields[fd.getFieldLabel()] = fd.getActiveWarnings();
             }
         }
         
         this._updateTabsStatus(false);
-        return invalidFields;
+        return warnedFields;
     },
     
     /**
@@ -2672,7 +2654,7 @@
         for (var i = 0; i < fieldNames.length; i++)
         {
             name = fieldNames[i];
-            field = this.getField(name);
+            field = this.getForm().findField(name);
             
             // remove the field name prefix and replace '.' by '/' to compute the path
             path = name.substring(this.getFieldNamePrefix().length).replace(/\./g, '/');
@@ -2727,7 +2709,7 @@
         for (var name in commentsMap)
         {
             fieldComments = commentsMap[name];
-            field = this.getField(name);
+            field = this.getForm().findField(name);
             this._setMetadataCommentsForFieldJSON(field, fieldComments);
         }
     },
@@ -2771,11 +2753,12 @@
      */
     getMessageTargetConf: function()
     {
+    	var form = this.getForm();
         var messageTargets = {
             'type': Ametys.message.MessageTarget.FORM,
             
             'parameters': {
-                'form': this,
+                'object': form,
                 'test-results': this._paramCheckersDAO ? this._paramCheckersDAO._testResults : {}
             }
         };
@@ -2786,7 +2769,7 @@
                 'type': Ametys.message.MessageTarget.FORM_FIELD,
                 
                 'parameters': {
-                            name: this.getField(this._focusFieldId).getName()
+                   name: form.findField(this._focusFieldId).getName()
                  }
             }
             
@@ -3054,12 +3037,12 @@
     _initializeDisableConditions: function()
     {
         var me = this;
-
+        
         // Disable conditions listeners
-        Ext.Array.each(this.getFieldNames(), function(fieldName) {
-            var field = me.getField(fieldName); 
+        Ext.Object.each(this.getForm().getValues(), function(fieldName) {
+        	var field = me.getForm().findField(fieldName); 
             
-            // fields that are initially disabled (cf can-not-write configuration) can never be enabled
+            // fields that are initially disabled can never be enabled See #cfg-helpBoxId
             if (field.disableCondition != null && !field.disabled)
             {
                 me._disableField(field);
@@ -3097,7 +3080,7 @@
                 conditionListLength = conditionList.length;
             for (var i = 0; i < conditionListLength; i++)
             {
-                var field = this.getField(conditionList[i]['id']);
+                var field = this.getForm().findField(conditionList[i]['id']);
                 field.on('change', Ext.bind(this._disableField, this, [disablingField], false));
             }
         }
@@ -3169,7 +3152,7 @@
      */
     _evaluateCondition: function(id, operator, value)
     {
-        var fieldValue = this.getField(id).getValue();
+        var fieldValue = this.getForm.findField(id).getValue();
         
         switch (operator)
         {
Index: main/plugin-core-ui/resources/js/Ametys/form/ConfigurableFormPanel/ParameterChecker.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/form/ConfigurableFormPanel/ParameterChecker.js	(revision 32647)
+++ main/plugin-core-ui/resources/js/Ametys/form/ConfigurableFormPanel/ParameterChecker.js	(working copy)
@@ -102,7 +102,9 @@
 		/** @cfg {String} buttonId The id of the button that launch the test */
 		buttonId: null,
 		/** @cfg {String} helpBoxId The id of the help icon associated to the test */
-		helpBoxId: null
+		helpBoxId: null,
+		/** @cfg {String} statusCmpId The id of the status component associated to the test */
+		statusCmpÎd: null
 	},
 	
 	constructor: function(paramChecker, uiRefLabel, uiRefType, label, description)
@@ -184,6 +186,15 @@
 	},
 	
 	/**
+	 * Set the id of the status component
+	 * @param {String} id See the #cfg-status-cmp-id
+	 */
+	setStatusCmpId: function(id)
+	{
+		this.statusCmpId = id;
+	},
+	
+	/**
 	 * Set the labels of the linked parameter
 	 * @param {Number} labelList See the #cfg-linkedParamsLabels
 	 */
Index: main/plugin-core-ui/resources/js/Ametys/form/ConfigurableFormPanel/ParameterCheckersDAO.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/form/ConfigurableFormPanel/ParameterCheckersDAO.js	(revision 32647)
+++ main/plugin-core-ui/resources/js/Ametys/form/ConfigurableFormPanel/ParameterCheckersDAO.js	(working copy)
@@ -23,13 +23,34 @@
  *  - a category
  */
 Ext.define('Ametys.form.ConfigurableFormPanel.ParameterCheckersDAO', {
-
+	
+	statics: {
+		/**
+		 * @property {Number} AMETYS_DESCRIPTION_WIDTH The width for Ametys descriptions
+		 * @private
+		 * @readonly 
+		 */
+		AMETYS_DESCRIPTION_WIDTH: 20,
+		
+		/**
+		 * @property {Number} PARAM_CHECKER_STATUS_CMP_WIDTH The width for components reflecting the parameter checkers' status
+		 * @private
+		 * @readonly 
+		 */
+		PARAM_CHECKER_STATUS_CMP_WIDTH: 20,
+		
+		/**
+		 * @property {Number} PARAM_CHECKER_STATUS_CMP_HEIGHT The height for components reflecting the parameter checkers' status
+		 * @private
+		 * @readonly 
+		 */
+		PARAM_CHECKER_STATUS_CMP_HEIGHT: 20
+	},
+	
 	/**
-	 * @property {Number} AMETYS_DESCRIPTION_WIDTH The width for ametys descriptions
+	 * @property {Ametys.form.ConfigurableFormPanel} _form the configurable form panel
 	 * @private
-	 * @readonly 
 	 */
-	AMETYS_DESCRIPTION_WIDTH: 20,
 	
 	/**
 	 * @property {Ametys.form.ConfigurableFormPanel.ParameterChecker[]} _paramCheckers The list of parameter checkers
@@ -60,8 +81,8 @@
 	 */
 	constructor: function(config)
 	{
-		this.form = config.form;
-		this.form.on('formready', Ext.bind(this._resumeEvents, this));
+		this._form = config.form;
+		this._form.on('formready', Ext.bind(this._resumeEvents, this));
 		
 		this._paramCheckers = [];
 	},
@@ -80,12 +101,14 @@
 	 * @param {String[]} paramChecker.linked-params the ids of the parameters used for the checking
 	 * @param {String} paramChecker.label the label of the parameter checker
 	 * @param {String} paramChecker.description the description of the parameter checker
+	 * @param {Number} offset The parameter checker offset
+     * @param {Number} roffset The parameter checker right offset
 	 */
-	addCategoryChecker: function(tab, paramChecker)
+	addCategoryChecker: function(tab, paramChecker, offset, roffset)
 	{
 		var parameterChecker = new Ametys.form.ConfigurableFormPanel.ParameterChecker(paramChecker, tab.title, "category", paramChecker.label, paramChecker.description, Ametys.form.ConfigurableFormPanel.ParameterChecker.STATUS_NOT_TESTED),
-			testButton = this._generateTestButton(parameterChecker),
-			testContainer = this._generateTestContainer(parameterChecker, testButton);
+			testButton = this._generateTestButton(parameterChecker, offset),
+			testContainer = this._generateTestContainer(parameterChecker, testButton, offset, roffset);
 		
 		tab.add(testContainer);
 		
@@ -105,8 +128,10 @@
 	 * @param {String} paramChecker.large-icon-path the path to the large icon representing the parameter checker
 	 * @param {String} paramChecker.label the label of the group the parameter checker is attached to
 	 * @param {String} paramChecker.description the description of the parameter checker
+	 * @param {Number} offset The parameter checker offset
+     * @param {Number} roffset The parameter checker right offset
 	 */
-	addGroupChecker: function(fieldset, paramChecker)
+	addGroupChecker: function(fieldset, paramChecker, offset, roffset)
 	{
 		var fieldsetCt = fieldset.up();
 		
@@ -117,8 +142,8 @@
     	}
 		
 		var parameterChecker = new Ametys.form.ConfigurableFormPanel.ParameterChecker(paramChecker, uiRefLabel, "group", paramChecker.label, paramChecker.description),
-		    testButton = this._generateTestButton(parameterChecker),
-		    testContainer = this._generateTestContainer(parameterChecker, testButton);
+		    testButton = this._generateTestButton(parameterChecker, offset),
+		    testContainer = this._generateTestContainer(parameterChecker, testButton, offset, roffset);
 		
 		fieldset.add(testContainer);
 		
@@ -138,13 +163,15 @@
 	 * @param {String[]} paramChecker.linked-params the ids of the parameters used for the checking
 	 * @param {String} paramChecker.label the label of the parameter checker
 	 * @param {String} paramChecker.description the description of the parameter checker
+	 * @param {Number} offset The parameter checker offset
+     * @param {Number} roffset The parameter checker right offset
 	 */
-	addParameterChecker: function(paramChecker)
+	addParameterChecker: function(paramChecker, offset, roffset)
 	{
-		var field = this.form.getField(paramChecker['param-ref']),
+		var field = this._form.getForm().findField(paramChecker['param-ref']),
 		    uiRefLabel = field.getFieldLabel(),
-	    	fieldCt = field.up(),
-	    	fieldCtParent = fieldCt.up();
+		    fieldCt = field.up(),
+		    groupPanel = field.up('panel');
 		
 	    if (Ext.String.startsWith(uiRefLabel, "*"))
     	{
@@ -156,16 +183,16 @@
 	    	uiRefLabel = fieldCt.title + "/" + uiRefLabel;
     	}
 	    
-	    if (fieldCtParent.hasCls('ametys-form-tab-item'))
+	    if (groupPanel.hasCls('ametys-form-tab-item'))
     	{
-	    	uiRefLabel = fieldCtParent.title + "/" + uiRefLabel;
+	    	uiRefLabel = groupPanel.title + "/" + uiRefLabel;
     	}
 	    
 	    var parameterChecker =  new Ametys.form.ConfigurableFormPanel.ParameterChecker(paramChecker, uiRefLabel, "parameter", paramChecker.label, paramChecker.description),
-	    	testButton = this._generateTestButton(parameterChecker),
-	    	testContainer = this._generateTestContainer(parameterChecker, testButton);
-	    
-	    fieldCt.add(testContainer);
+	    	testButton = this._generateTestButton(parameterChecker, offset),
+	    	testContainer = this._generateTestContainer(parameterChecker, testButton, offset, roffset);
+    	
+	    groupPanel.add(testContainer);
 	    
 		this._paramCheckers.push(parameterChecker);
 	},
@@ -179,7 +206,6 @@
 		this._isSuspended = false;
 	},
 	
-	
 	/**
 	 * Initialize the listeners on linked parameters
      * To be called once fields are created
@@ -194,18 +220,19 @@
 			var linkedParamsLabels = [];
 			Ext.Array.each(paramChecker.linkedParams, function(linkedParam) {
 
-				var	linkedParamField = me.form.getField(linkedParam);
+				var	linkedParamField = me._form.getForm().findField(linkedParam);
 				var fieldLabel = linkedParamField.getFieldLabel();
 				
 				linkedParamField.on('change', Ext.bind(me._updateTestButton, me, [paramChecker], false));
 				linkedParamField.on('disable', Ext.bind(me._updateTestButton, me, [paramChecker], false));
 				linkedParamField.on('enable', Ext.bind(me._updateTestButton, me, [paramChecker], false));
-				linkedParamField.on('warningchange', Ext.bind(me.form._updateTabsStatus, me.form));
+				
+				linkedParamField.on('warningchange', Ext.bind(me._form._updateTabsStatus, me._form, [false], false));
 			})
 		});
 		
 		// Initialize test results 
-		this.form.on({
+		this._form.on({
 			// Update test results after the parameter checkers are created
 			formready: {fn: this._updateTestResults, scope: this, single: true, order: 'after'} 
 		});
@@ -215,20 +242,29 @@
 	 * @private
 	 * Generates the button launching the tests on the corresponding parameter/group/category
 	 * @param {Ametys.form.ConfigurableFormPanel.ParameterChecker} paramChecker the parameter checker 
+	 * @param {Number} offset the parameter checker offset
 	 */
-	_generateTestButton: function(paramChecker)
+	_generateTestButton: function(paramChecker, offset)
 	{	
 		var me = this;
 		
 		return Ext.create('Ext.button.Button', {
 			id: Ext.id(),
-			paramChecker: paramChecker,
-			tooltip: '',
-			icon: Ametys.CONTEXT_PATH + paramChecker.smallIconPath,
+			
 			text: "<i18n:text i18n:key='PLUGINS_CORE_UI_CONFIGURABLE_FORM_PARAM_CHECKER_TEXT_PREFIX'/>" + " \"" + paramChecker.label + "\"",
-			cls: 'param-checker-component',
-            border: false,
-            handler: function(btn, event)
+			textAlign: 'left',
+			
+			icon: Ametys.CONTEXT_PATH + paramChecker.smallIconPath,
+			cls: 'param-checker-button',
+			
+			flex: 1,
+			minWidth: Ametys.form.ConfigurableFormPanel.LABEL_WIDTH - offset + Ametys.form.ConfigurableFormPanel.FIELD_MINWIDTH,
+
+			border: false,
+            tooltip: '',
+            
+            paramChecker: paramChecker,
+            handler: function()
             {
         		me.check([paramChecker], false);
             }
@@ -240,24 +276,56 @@
 	 * Generates the container that holds the test button and the ametys description
 	 * @param {Ametys.form.ConfigurableFormPanel.ParameterChecker} paramChecker the parameter checker 
 	 * @param {Ext.button.Button} testButton the test button
+	 * @param {Number} offset the offset of the parameter checker
+	 * @param {Number} roffset the right offset of the parameter checker
 	 * @return {Ext.container.Container} the container with the test button and the help box
 	 */
-	_generateTestContainer: function(paramChecker, testButton)
+	_generateTestContainer: function(paramChecker, testButton, offset, roffset)
 	{
-		var me = this,
-			helpBoxId = Ext.id(),
-			items = [testButton];
-			
+		var me = this;
+		
+		var items = [];
+		
+		// Spacer "pushing" the button
+		items.push({
+			xtype: 'tbspacer', 
+			width: Ametys.form.ConfigurableFormPanel.LABEL_WIDTH - offset + Ametys.form.ConfigurableFormPanel.LABEL_PADDING
+		});
+		
+		// the button itself
+		items.push(testButton);
 		paramChecker.setButtonId(testButton.getId());
+		
+		var helpBoxId = Ext.id();
 		paramChecker.setHelpBoxId(helpBoxId);
 		
+		var paramCheckerStatusCmpId = Ext.id();
+		paramChecker.setStatusCmpId(paramCheckerStatusCmpId);
+		
+		// component initially hidden that will reflect the status of the check
 		items.push({
-			id: helpBoxId,
+			xtype: 'component',
+			id: paramCheckerStatusCmpId,
+			
+			height: Ametys.form.ConfigurableFormPanel.ParameterCheckersDAO.PARAM_CHECKER_STATUS_CMP_WIDTH,
+			width: Ametys.form.ConfigurableFormPanel.ParameterCheckersDAO.PARAM_CHECKER_STATUS_CMP_WIDTH,
+			
+			hidden: true,
+			cls: 'param-checker-status'
+		});
+		
+		// the description
+		items.push({
 			xtype: 'component', 
+
+			id: helpBoxId,
 			cls: "ametys-description",
-		    baseCls: '', 
-		    padding: "3 0 0 5",
+		    
+			baseCls: '', 
+		    
 			width: Ametys.form.ConfigurableFormPanel.ParameterCheckersDAO.AMETYS_DESCRIPTION_WIDTH,
+			padding: "3 0 0 5",
+
 			listeners: {
 				'render': function() {
 					this.getEl().set({"data-qtip": me._generateHelpBoxTip(paramChecker)});
@@ -268,9 +336,11 @@
 		return Ext.create('Ext.Container', {
 			layout: {
 				type: 'hbox',
-				pack:'end'
+				align: 'stretch',
+				pack: 'start'
 			},
-			cls : 'param-checker-container',
+			style: 'margin-right:' + Math.max(60 - roffset, 0) + 'px',
+			cls: 'param-checker-container',
 			items: items
 		});
 	},
@@ -278,7 +348,7 @@
 	/**
 	 * @private
 	 * Generates the HTML code for the parameter checker's help box tooltip.
-	 * @param {Ametys.plugins.admin.Config.ParameterChecker} paramChecker the parameter checker 
+	 * @param {Ametys.form.ConfigurableFormPanel.ParameterChecker} paramChecker the parameter checker 
 	 */
 	_generateHelpBoxTip: function(paramChecker)
     {
@@ -287,7 +357,7 @@
 		
 		// Set the labels of the linked parameters in the param checker
 		Ext.Array.each(paramChecker.linkedParams, function(linkedParam) {
-			var	linkedParamField = me.form.getField(linkedParam);
+			var	linkedParamField = me._form.getForm().findField(linkedParam);
 			var fieldLabel = linkedParamField.getFieldLabel();
 			
 			if (Ext.String.startsWith(fieldLabel,"*"))
@@ -304,7 +374,7 @@
 			'<table>' + 
 			  	  '<tr>' +
 		  	  		  '<td style="width: 48px">' +
-						  '<img width="48" height="48" src=/plugins/' + paramChecker.plugin + '/resources/' + paramChecker.largeIconPath + '/>' +
+						  '<img width="48" height="48" src=' + Ametys.CONTEXT_PATH + paramChecker.largeIconPath + '/>' +
 				  	  '</td>' +
 		  	  		  '<td>' +
 						  '<strong> &#008; &#008; ' + paramChecker.label + ' </strong>' +
@@ -382,7 +452,7 @@
 			targets: {
 				type: Ametys.message.MessageTarget.CONFIGURATION,
 				parameters: {},
-				subtargets: [ this.form.getMessageTargetConf() ]
+				subtargets: [ this._form.getMessageTargetConf() ]
 			}
 		});
 	},
@@ -390,7 +460,7 @@
 	/**
 	 * @private
 	 * Sets the test button to the "warning" status, updates the button's tooltip and the test results panel
-	 * @param {Ametys.plugins.admin.Config.ParameterChecker} paramChecker the parameter checker to be updated
+	 * @param {Ametys.form.ConfigurableFormPanel.ParameterChecker} paramChecker the parameter checker to be updated
 	 */	
 	_updateTestButton: function(paramChecker)
 	{
@@ -400,6 +470,7 @@
 		}
 		
 		var btn = Ext.getCmp(paramChecker.buttonId),
+			statusCmp = Ext.getCmp(paramChecker.statusCmpId),
 			helpBox = Ext.getCmp(paramChecker.helpBoxId),
 			linkedParams = paramChecker.linkedParams,
 		    allLinkedParamsDisabled = true,
@@ -408,7 +479,7 @@
 	    for (var i = 0; i < linkedParams.length; i++)
 		{
 			var linkedParam = linkedParams[i],
-				linkedParamField = this.form.getField(linkedParam);
+				linkedParamField = this._form.getForm().findField(linkedParam);
 
 			// check if all linked parameters are disabled
 			if (!linkedParamField.isDisabled())
@@ -422,12 +493,11 @@
 				invalidLinkedParameter = true;
 			}
 		}
-
+	    
 	    // update style
-	    btn.removeCls(['success', 'failure']);
-	    btn.addCls('warning');
-	    helpBox.addCls('offset');
-	    btn.up('container').updateLayout();
+	    statusCmp.setVisible(true);
+	    statusCmp.removeCls(['success', 'failure']);
+	    statusCmp.addCls('warning');
 	    
 	    if (!allLinkedParamsDisabled && !invalidLinkedParameter)
     	{
@@ -441,19 +511,19 @@
     	}
 	    
 	    // update the tooltip
-	    this._generateStatusTip(btn.up('container').getEl(), paramChecker);
+	    this._generateStatusTip(statusCmp.getEl(), paramChecker);
 	    helpBox.getEl().set({"data-qtip": this._generateHelpBoxTip(paramChecker)});
 	    
 	    // update the test results 
 	    this._updateTestResults();
-	    this.form._updateTabsStatus();
+	    this._form._updateTabsStatus();
 	},
 	
 	/**
 	 * @private
 	 * Generates the HTML code for the parameter checker's status tooltip.
 	 * @param {Ext.Element} el The element to add tooltips on
-	 * @param {Ametys.plugins.admin.Config.ParameterChecker} paramChecker the parameter checker 
+	 * @param {Ametys.form.ConfigurableFormPanel.ParameterChecker} paramChecker the parameter checker 
 	 */
 	_generateStatusTip: function(el, paramChecker)
     {
@@ -527,7 +597,7 @@
 				warningMsg = "<i18n:text i18n:key='PLUGINS_CORE_UI_CONFIGURABLE_FORM_PARAM_CHECKER_WARNING_TEXT_BEGINNING'/>" + paramChecker.label + "<i18n:text i18n:key='PLUGINS_CORE_UI_CONFIGURABLE_FORM_PARAM_CHECKER_WARNING_TEXT_END'/>";	
 			
 			Ext.Array.each(paramChecker.linkedParams, function(linkedParam){
-				var linkedParamField = me.form.getField(linkedParam);
+				var linkedParamField = me._form.getForm().findField(linkedParam);
 				linkedParamField._warnings = linkedParamField._warnings || {};
 				
 				var activeWarnings = linkedParamField.getActiveWarnings();
@@ -548,10 +618,6 @@
 					
 					linkedParamField.markWarning(activeWarnings);
 				}
-				else if (warning)
-				{
-					helpBox.addCls('offset');
-				}
 			});
 		});
 	},
@@ -619,7 +685,7 @@
      */
     check: function(paramCheckers, displayErrors, callback, forceTest)
     {
-        var form = this.form;
+        var form = this._form;
         
         forceTest = forceTest !== false ? true : false;
         
@@ -659,13 +725,13 @@
      * @param {Object} options.params the call parameters
      * @param {boolean} success true if the request succeeded, false otherwise
      * @param {Object} response the server's response
-     * @param {Ametys.plugins.admin.Config.ParameterChecker[]} paramCheckers the parameter checker
+     * @param {Ametys.form.ConfigurableFormPanel.ParameterChecker[]} paramCheckers the parameter checker
      * @param {Function} callback the optional callback for this function.
      * @param {Boolean} displayErrors true if the errors have to be displayed at then end of the tests
      */
     _checkCb: function(options, success, response, paramCheckers, callback, displayErrors)
     {   
-        var form = this.form;
+        var form = this._form;
         
         var paramCheckersDAO = form._paramCheckersDAO;
         if (!success)
@@ -693,40 +759,36 @@
         Ext.Array.each(paramCheckers, function(paramChecker) {
             var btn = Ext.getCmp(paramChecker.buttonId),
                 helpBox = Ext.getCmp(paramChecker.helpBoxId),
+                statusCmp = Ext.getCmp(paramChecker.statusCmpId),
                 errorMsg = Ext.dom.Query.selectValue("*/" + paramChecker.id, result);
                 
             btn.getEl().unmask();
             btn.enable();
-            btn.removeCls('warning');
+            
+            statusCmp.setVisible(true);
+            statusCmp.removeCls(['failure', 'warning', 'success']);
             
             // Update the parameter checker's component
             if (errorMsg == null)
             {
-                btn.removeCls('failure');
-                btn.addCls('success');
-                helpBox.addCls('offset');
+            	statusCmp.addCls('success');
                 paramChecker.setStatus(Ametys.form.ConfigurableFormPanel.ParameterChecker.STATUS_SUCCESS);
-                paramChecker.setErrorMsg(null);
-                
-                paramCheckersDAO._generateStatusTip(btn.up('container').getEl(), paramChecker);
-                helpBox.getEl().set({"data-qtip": paramCheckersDAO._generateHelpBoxTip(paramChecker)});
             }
             else
             {
                 errors++;
-                btn.removeCls('success');
-                btn.addCls('failure');
-                helpBox.addCls('offset');
-                paramChecker.setStatus(Ametys.form.ConfigurableFormPanel.ParameterChecker.STATUS_FAILURE);
-                paramChecker.setErrorMsg(errorMsg);
+                statusCmp.addCls('failure');
                 
-                paramCheckersDAO._generateStatusTip(btn.up('container').getEl(), paramChecker);
-                helpBox.getEl().set({"data-qtip": paramCheckersDAO._generateHelpBoxTip(paramChecker)});
+                paramChecker.setStatus(Ametys.form.ConfigurableFormPanel.ParameterChecker.STATUS_FAILURE);
             }
+            
+            paramChecker.setErrorMsg(errorMsg);
+            paramCheckersDAO._generateStatusTip(statusCmp.getEl(), paramChecker);
+            helpBox.getEl().set({"data-qtip": paramCheckersDAO._generateHelpBoxTip(paramChecker)});
         });
         
         paramCheckersDAO._updateTestResults();
-        paramCheckersDAO.form._updateTabsStatus(); 
+        paramCheckersDAO._form._updateTabsStatus(); 
         paramCheckersDAO._updateWarnings();
         
         if (callback && typeof callback === 'function') 
Index: main/plugin-core-ui/resources/js/Ametys/form/SaveErrorDialog.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/form/SaveErrorDialog.js	(revision 32647)
+++ main/plugin-core-ui/resources/js/Ametys/form/SaveErrorDialog.js	(working copy)
@@ -18,7 +18,7 @@
  * This UI helper provides a dialog box to display errors while saving
  * See #showErrorDialog method
  */
-Ext.define('Ametys.helper.SaveErrorDialog', {
+Ext.define('Ametys.form.SaveErrorDialog', {
 	singleton: true,
 	
 	/**
Index: main/plugin-core-ui/resources/js/Ametys/form/SaveHelper.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/form/SaveHelper.js	(revision 0)
+++ main/plugin-core-ui/resources/js/Ametys/form/SaveHelper.js	(revision 0)
@@ -0,0 +1,246 @@
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+/**
+ * Singleton class checking the validity of a form in order to save it
+ */
+Ext.define('Ametys.form.SaveHelper', {
+    singleton: true,
+    
+    /**
+     * @property {Number} LAUNCH_TESTS_SAVE_MBOX_HEIGHT The height of the "launch tests and save" message box
+     * @private
+     * @readonly 
+     */
+    LAUNCH_TESTS_SAVE_MBOX_HEIGHT: 130,
+    
+    /**
+     * Get the form from message targets
+     * @param {Ametys.message.MessageTarget[]} targets the message targets
+     * @return the form is found, null otherwise
+     */
+    getForm: function(targets)
+    {
+        var target = targets[0];
+        if (target != null)
+        {
+            var subtargets = target.getSubtargets(function(target) { return target.getType() ==  Ametys.message.MessageTarget.FORM }, 0);
+            if (subtargets.length == 0)
+            {
+            	Ext.MessageBox.alert("<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_NOFORM_TITLE'/>", "<i18n:text i18n:key='PLUGINS_CORE_UI_CONFIG_SAVE_NOFORM'/>");
+                return null;
+            }
+
+            return subtargets[0].getParameters().object.owner;
+        }
+        
+        return null;
+    },
+    
+    /**
+     * Saving of a configuration or a content
+     * @param {Ametys.form.ConfigurableFormPanel} form the configurable form panel
+     * @param {Function} callback function invoked after the saving process was authorized or not
+     */
+    canSave: function(form, callback)
+    {
+        var me = this,
+	        testsOk = true,
+	        paramCheckersDAO = form._paramCheckersDAO,
+	        paramCheckers = paramCheckersDAO._paramCheckers;
+        
+		// Validate form
+		var invalidFields = Ext.Array.merge(form.getInvalidFields(), form.getInvalidRepeaters());
+		if (invalidFields.length > 0)
+		{
+			var errorMessage = "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_ACTION_INVALIDFIELDS'/>";
+			errorMessage += "<ul>";
+			Ext.Array.each(invalidFields, function(invalidField) {
+				errorMessage += "<li>";
+				errorMessage += invalidField
+				errorMessage += "</li>";
+			})
+			errorMessage += "</ul>";
+			
+			Ametys.Msg.show({
+				   title: "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_ACTION_INVALID_TITLE'/>", 
+				   msg: errorMessage,
+				   buttons: Ext.Msg.OK,
+				   icon: Ext.MessageBox.ERROR,
+				   handler: function() {callback(false);}  
+			});
+			
+			return;
+		}
+		
+        // At least one test wasn't successful
+        if (!this._areTestsOk(paramCheckers))
+        {
+            var msgBox = Ext.create('Ext.window.MessageBox', {closeAction: 'destroy'});
+            
+            msgBox.buttonText.yes = "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_SAVE'/>";
+            msgBox.buttonText.no = "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_RETRY'/>";
+            msgBox.buttonText.cancel = "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_CANCEL'/>";
+            msgBox.show({
+                title: "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_TITLE'/>", 
+                msg: "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_TESTS_NOK_MBOX_MSG'/>",
+                buttons: Ext.Msg.YESNOCANCEL,
+                icon: Ext.Msg.ERROR,
+                height: me.LAUNCH_TESTS_SAVE_MBOX_HEIGHT,
+                fn: function(answer)
+                {
+                    if (answer == 'yes')
+                    {
+                        callback(true);
+                    }
+                    else if (answer == 'no')
+                    {
+                        Ext.getBody().mask("<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_WAIT_MSG'/>");
+                        paramCheckersDAO.check(paramCheckers, 
+            					              true, 
+            					              Ext.bind(function(success) 
+    					            		  { 
+        					            	  	Ext.getBody().unmask(); 
+        					            	  	if (success) 
+        					            	  	{ 
+        					            			this._handleWarnedFields(form, callback);
+    					            	  		} 
+    					            		  }, me), false);
+                    }
+                }
+            });
+            return;
+        }
+        // All tests passed, check the warning on fields
+        else
+    	{
+        	this._handleWarnedFields(form, callback);
+    	}
+    },
+   
+    /**
+     * Display a dialog box listing the errors found by the server
+     * @param {Ametys.form.ConfigurableFormPanel} form the configurable form panel
+     * @param {String} errorTitle the title of the save error dialog
+     * @param {Ametys.form.ConfigurableFormPanel} errorMsg the introduction message of the error dialog
+     * @param {Object} fieldErrors the mapping of field names with the corresponding error message
+     * @param {Array} globalErrors the global error messages
+     */
+    handleServerErrors: function(form, errorTitle, errorMsg, fieldErrors, globalErrors)
+    {
+    	var fieldNamePrefix = form.getFieldNamePrefix();
+    	var detailedMsg = '';
+    	
+    	// First treat the global errors
+    	if (globalErrors && globalErrors.length > 0)
+    	{
+    		for (var i=0; i < globalErrors.length; i++)
+    		{
+    			detailedMsg += '<li>' + globalErrors[i] + '</li>'
+    		}
+    	}
+    	
+    	// Then treat the errors on fields
+    	if (!Ext.Object.isEmpty(fieldErrors))
+    	{
+    		for (var name in fieldErrors)
+    		{
+    			var fieldName = fieldNamePrefix + name;
+    			var fd = form.getForm().findField(fieldName);
+    			
+    			detailedMsg += '<li><b>' + (fd != null ? fd.getFieldLabel() : fieldName) + '</b>: ' + fieldErrors[name] + '</li>';
+    		}
+    		
+    		form.markFieldsInvalid (fieldErrors);
+    	}
+    
+    	Ametys.form.SaveErrorDialog.showErrorDialog (errorTitle, errorMsg, '<ul>' + detailedMsg + '</ul>');
+    },
+    
+    /**
+     * @private
+     * Display a dialog box listing the fields in warning
+     * @param {Ametys.form.ConfigurableFormPanel} form the configurable form panel
+     * @param {Function} callback the callback function
+     */
+    _handleWarnedFields: function(form, callback)
+    {
+    	var warnedFields = form.getWarnedFields();
+		if (Ext.Object.getSize(warnedFields) > 0)
+		{
+			var msgBox = Ext.create('Ext.window.MessageBox', {closeAction: 'destroy'});
+            msgBox.buttonText.yes = "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_ACTION_WARNED_FIELDS_YES'/>";
+            msgBox.buttonText.no = "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_ACTION_WARNED_FIELDS_NO'/>";
+			
+			var message = "<ul>";
+			Ext.Object.each(warnedFields, function(warnedFieldLabel) {
+				var warningMessages = warnedFields[warnedFieldLabel];
+				
+				var warningList = "<ul>";
+				Ext.Array.each(warningMessages, function(warningMessage) {
+					warningList += "<li>";
+					warningList += warningMessage;
+					warningList += "</li>";
+				});
+				warningList += "</ul>";
+				
+				message += "<li>";
+				message += "<strong>" + warnedFieldLabel + "</strong>: " + warningList
+				message += "</li>";
+			})
+			message += "</ul>";
+			
+		    Ametys.Msg.show({
+		        title: "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_ACTION_WARNED_FIELDS_TITLE'/>",
+		        msg: "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_ACTION_WARNED_FIELDS_START'/>" + message + "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_ACTION_WARNED_FIELDS_END'/>",
+		        buttons: Ext.Msg.YESNO,
+		        icon: Ext.Msg.QUESTION,
+		        fn: function(answer) {callback(answer == 'yes');},
+		        scope: this
+		    });
+		    return;
+		}
+		else
+		{
+			// All checks passed successfully
+	  		callback(true); 
+		}
+    },
+    
+    /**
+     * @private
+     * Are all the tests currently successful ?
+     * @param {Ametys.form.ConfigurableFormPanel.ParameterChecker[]} paramCheckers the parameter checkers
+     * @return true if all the tests are currently successful, false otherwise
+     */
+    _areTestsOk: function(paramCheckers)
+    {
+    	var testsOk = true;
+		Ext.Array.each(paramCheckers, function(paramChecker) {
+	        var status = paramChecker.getStatus();
+	        
+	        if (Ext.getCmp(paramChecker.buttonId).isVisible() 
+	            && (status == Ametys.form.ConfigurableFormPanel.ParameterChecker.STATUS_FAILURE 
+	                || status == Ametys.form.ConfigurableFormPanel.ParameterChecker.STATUS_NOT_TESTED
+	                || status == Ametys.form.ConfigurableFormPanel.ParameterChecker.STATUS_WARNING))
+	        {
+	        	testsOk = false;
+	        }
+	    });
+		
+		return testsOk;
+    }
+});
\ No newline at end of file
Index: main/plugin-core-ui/resources/js/Ametys/form/field/ChangePassword.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/form/field/ChangePassword.js	(revision 32647)
+++ main/plugin-core-ui/resources/js/Ametys/form/field/ChangePassword.js	(working copy)
@@ -179,7 +179,7 @@
     	// Change password button
     	var buttonConfig = this.buttonConfig || {};
     	Ext.applyIf(buttonConfig, {
-    		text: "<i18n:text i18n:key='PLUGINS_CORE_UI_PASSWORD_CHANGE'/>",
+    		text: "<i18n:text i18n:key='PLUGINS_CORE_UI_CHANGEPASSWORD_CHANGE'/>",
     		cls: 'ametys-changepassword-change',
     		width: '100%',
     		handler: Ext.bind(this._setToMode, this, [Ametys.form.field.ChangePassword.MODE_CHANGEPASSWORD])
@@ -189,7 +189,7 @@
     	// Reset change button
     	var rstButtonCfg = {
     		handler: Ext.bind(this._setToMode, this, [Ametys.form.field.ChangePassword.MODE_SEEPASSWORD]),
-    		tooltip: "<i18n:text i18n:key='PLUGINS_CORE_UI_PASSWORD_CLEAR'/>",
+    		tooltip: "<i18n:text i18n:key='PLUGINS_CORE_UI_CHANGEPASSWORD_CLEAR'/>",
     		cls: 'ametys-changepassword-reset',
     		height: 0
     	};
@@ -241,7 +241,7 @@
     	
     	if ((arguments.length == 1 && value == undefined) || (arguments.length == 0 && this.items.get(Ametys.form.field.ChangePassword.INDEX_MAIN_FIELD).getValue() != this.items.get(Ametys.form.field.ChangePassword.INDEX_CONFIRMATION_FIELD).getValue()))
     	{
-    		errors.push("<i18n:text key='PLUGINS_CORE_UI_PASSWORD_VALIDATOR'/>");
+    		errors.push("<i18n:text key='PLUGINS_CORE_UI_CHANGEPASSWORD_VALIDATOR'/>");
     	}
 
     	return errors;
Index: main/plugin-core-ui/resources/js/Ametys/form/field/DateTime.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/form/field/DateTime.js	(revision 32835)
+++ main/plugin-core-ui/resources/js/Ametys/form/field/DateTime.js	(working copy)
@@ -27,7 +27,7 @@
 	 */
     
     /**
-	 * @cfg {Object} timeConfig A configuration to transmit to the time field. See {@link Ext.form.field.Time
+	 * @cfg {Object} timeConfig A configuration to transmit to the time field. See {@link Ext.form.field.Time}
 	 */
     
     /**
Index: main/plugin-core-ui/resources/js/Ametys/form/field/Password.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/form/field/Password.js	(revision 0)
+++ main/plugin-core-ui/resources/js/Ametys/form/field/Password.js	(revision 0)
@@ -0,0 +1,339 @@
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+/**
+ * Field that displays allows to type in a password. If no password is set the field is empty, else
+ * the field is initially disabled.
+ */
+Ext.define('Ametys.form.field.Password', {
+    extend: 'Ametys.form.AbstractFieldsWrapper',
+    alias: ['widget.passwordfield', 'widget.password'],
+    alternateClassName: ['Ext.form.PasswordField', 'Ext.form.Password', 'Ext.form.field.Password'],
+    
+    statics: {
+    	
+    	/**
+         * @protected
+         * @readonly
+         * @property {Number} 
+         * the mode when no password has been defined yet
+         */
+        MODE_NOPASSWORD: 0,
+        /**
+         * @protected
+         * @readonly
+         * @property {Number} 
+         * the mode when setting a password 
+         */
+        MODE_CHANGEPASSWORD: 1,
+        /**
+         * @protected
+         * @readonly
+         * @property {Number} 
+         * the mode when displaying a password
+         */
+        MODE_SEEPASSWORD: 2,
+        /**
+         * @protected
+         * @readonly
+         * @property {String} 
+         * the tooltip of the button for changing the password
+         */
+        RESET_PASSWORD_TEXT: "<i18n:text i18n:key='PLUGINS_CORE_UI_PASSWORD_RESET'/>",
+        /**
+         * @protected
+         * @readonly
+         * @property {String} 
+         * the tooltip of the button for resetting the password
+         */
+        CHANGE_PASSWORD_TEXT: "<i18n:text i18n:key='PLUGINS_CORE_UI_PASSWORD_CHANGE'/>",
+        /**
+         * @protected
+         * @readonly
+         * @property {String} 
+         * the icon path for changing the password
+         */
+        CHANGE_PASSWORD_ICON_PATH: Ametys.getPluginResourcesPrefix('core-ui') + '/img/Ametys/theme/gray/reload_16.png',
+        /**
+         * @protected
+         * @readonly
+         * @property {String} 
+         * the icon path for resetting the password
+         */
+        RESET_PASSWORD_ICON_PATH: Ametys.getPluginResourcesPrefix('core-ui') + '/img/Ametys/theme/gray/password-reset.png',
+        /**
+         * @protected
+         * @readonly
+         * @property {Number} 
+         * the width of the button
+         */
+        SET_CLEAR_PASSWORD_BUTTON_WIDTH: 20,
+        /**
+         * @protected
+         * @readonly
+         * @property {Number} 
+         * the height of the button
+         */
+        SET_CLEAR_PASSWORD_BUTTON_HEIGHT: 20
+    },
+    
+    /**
+     * @cfg {Object} passwordConfig The configuration object for the first text field. Note that many configuration can be set directly here and will we broadcasted to underlying field (allowBlank...)
+     */
+    /**
+     * @cfg {Object} buttonConfig The configuration object for the button to change password.
+     */
+    /**
+     * @cfg {Boolean} allowBlank
+     * This property is copied to underlying text fields. See {@link Ext.form.field.Text#cfg-allowBlank}.
+     */
+    /**
+     * @cfg {Boolean} blankText
+     * This property is copied to underlying text fields. See {@link Ext.form.field.Text#cfg-blankText}.
+     */
+    /**
+     * @cfg {String} emptyText
+     * This property is copied to underlying text fields. See {@link Ext.form.field.Text#cfg-emptyText}.
+     */
+    /**
+     * @cfg {String} invalidText
+     * This property is copied to underlying text fields. See {@link Ext.form.field.Text#cfg-invalidText}.
+     */
+    /**
+     * @cfg {RegExp} maskRe
+     * This property is copied to underlying text fields. See {@link Ext.form.field.Text#cfg-maskRe}.
+     */
+    /**
+     * @cfg {Number} maxLength
+     * This property is copied to underlying text fields. See {@link Ext.form.field.Text#cfg-maxLength}.
+     */
+    /**
+     * @cfg {String} maxLengthText
+     * This property is copied to underlying text fields. See {@link Ext.form.field.Text#cfg-maxLengthText}.
+     */
+    /**
+     * @cfg {Number} minLength
+     * This property is copied to underlying text fields. See {@link Ext.form.field.Text#cfg-minLength}.
+     */
+    /**
+     * @cfg {String} minLengthText
+     * This property is copied to underlying text fields. See {@link Ext.form.field.Text#cfg-minLengthText}.
+     */
+    /**
+     * @cfg {RegExp} regex
+     * This property is copied to underlying text fields. See {@link Ext.form.field.Text#cfg-regex}.
+     */
+    /**
+     * @cfg {String} regexText
+     * This property is copied to underlying text fields. See {@link Ext.form.field.Text#cfg-regexText}.
+     */
+    /**
+     * @cfg {Boolean} selectOnFocus
+     * This property is copied to underlying text fields. See {@link Ext.form.field.Text#cfg-selectOnFocus}.
+     */
+    /**
+     * @cfg {Number} size
+     * This property is copied to underlying text fields. See {@link Ext.form.field.Text#cfg-size}.
+     */
+    /**
+     * @cfg {RegExp} stripCharsRe
+     * This property is copied to underlying text fields. See {@link Ext.form.field.Text#cfg-stripCharsRe}.
+     */
+
+	/**
+	 * @private
+	 * @property {Number} _mode The current mode of the widget. One of MODE_CHANGEPASSWORD, MODE_SEEPASSWORD and MODE_CHANGEPASSWORD.
+	 */
+    /**
+     * @private
+     * @property  {String} _previousValue the memorized value of the field
+     */
+    /**
+     * @private
+	 * @property {Ext.form.field.Text} _field the field containing the password
+	 */
+    /**
+     * @private
+	 * @property {Ext.button.Button} _button the button allowing to change/save the password's value
+	 */
+    /**
+     * @private
+     * @property  {Boolean} _initiallyInSeeMode true if the saved password is the good one, false otherwise
+     */
+    
+    initComponent: function() 
+    {
+    	// Password field
+    	var passwordConfig = this.passwordConfig || {};
+    		passwordConfig.inputType = 'password';
+    		passwordConfig.flex = 1;
+
+		var propertiesToCopy = ['allowBlank', 'blankText', 'emptyText', 'invalidText', 'maskRe', 'maxLength', 'maxLengthText', 'minLength', 'minLengthText', 'regex', 'regexText', 'selectOnFocus', 'size', 'stripCharsRe'];
+    	this._copyPropIfDefined(passwordConfig, propertiesToCopy, this.initialConfig);
+    	
+    	this._field = Ext.create('Ext.form.field.Text', passwordConfig);
+    	
+    	this._mode = Ametys.form.field.Password.MODE_NOPASSWORD;
+    	
+    	// Change password button 
+    	var buttonConfig = this.buttonConfig || {};
+    	
+    	Ext.applyIf(buttonConfig, {
+    		tooltip: '',
+    		width: Ametys.form.field.Password.SET_CLEAR_PASSWORD_BUTTON_WIDTH,
+    		height: Ametys.form.field.Password.SET_CLEAR_PASSWORD_BUTTON_HEIGHT,
+    		handler: Ext.bind(this._setToMode, this, [null], false),
+    		margin: '1 0 0 0',
+    		border: false
+    	});
+    	this._button = Ext.create('Ext.button.Button', buttonConfig);
+    	
+    	this.items = [this._field, this._button];
+    	
+    	this.msgTarget = 'side';
+    	
+    	this.on('afterrender', this._adaptRenderToMode, this);
+    	
+        this.callParent(arguments);
+    },
+    
+    getErrors: function (value) 
+    {
+    	var errors = [];
+    	if (!this.allowBlank && !this._field.getValue())
+    	{
+    		errors.push(this.blankText);
+    	}
+
+    	if (this._mode == Ametys.form.field.Password.MODE_SEEPASSWORD)
+    	{
+    		return errors;
+    	}
+
+    	return errors;
+    },
+    
+    getValue: function()
+    {
+        if (this._mode == Ametys.form.field.Password.MODE_SEEPASSWORD)
+    	{
+    		return null;
+    	}
+    	else 
+    	{
+    		return this._field.getValue();
+    	}
+    },
+    
+    getSubmitData: function ()
+    {
+    	return this._mode == Ametys.form.field.Password.MODE_SEEPASSWORD ? null : this.callParent(arguments);
+    },
+    
+    setValue: function(value)
+    {
+		this._setToMode((value == undefined || value == '') ? Ametys.form.field.Password.MODE_NOPASSWORD : Ametys.form.field.Password.MODE_SEEPASSWORD);
+    		
+		this._field.setValue(value);
+    	
+    	this.callParent(arguments);
+    },
+    
+    enable: function()
+    {
+    	this._adaptRenderToMode();
+
+    	this.callParent(arguments);
+    },
+    
+    disable: function()
+    {
+    	this._adaptRenderToMode();
+
+    	this.callParent(arguments);
+    },
+    
+    /**
+     * @private
+     * Change the mode
+     * @param {Number} mode The mode to set (see {@link #property-mode}). The render is modified.
+     */
+    _setToMode: function(mode) 
+    {
+    	if (mode == null && this._initiallyInSeeMode && this._mode == Ametys.form.field.Password.MODE_CHANGEPASSWORD)
+		{
+    		// we should not be able to go in MODE_SEEPASSWORD if the field was not initially in that mode
+    		mode = Ametys.form.field.Password.MODE_SEEPASSWORD;
+		}
+    	
+    	if (this._mode == Ametys.form.field.Password.MODE_SEEPASSWORD)
+    	{
+    		if (mode == null)
+			{
+    			mode = Ametys.form.field.Password.MODE_CHANGEPASSWORD;
+			}
+    		
+        	this._previousValue = this._field.getValue();
+        	this._initiallyInSeeMode = true;
+    	}
+    	
+    	this._mode = mode;
+
+    	if (mode == Ametys.form.field.Password.MODE_SEEPASSWORD)
+    	{
+    		this._field.setValue(this._previousValue);
+    	}
+
+    	this._adaptRenderToMode();
+    	this.clearInvalid();
+    },
+    
+    /**
+     * @private
+     * Modify the render to adapt it to the current mode and current enable state
+     */
+    _adaptRenderToMode: function() 
+    {
+    	if (!this.rendered)
+    	{
+    		return;
+    	}
+
+    	if (this._mode == Ametys.form.field.Password.MODE_NOPASSWORD)
+		{
+    		this._field.setDisabled(this.disabled);
+    		this._button.hide();
+		}
+    	else
+		{
+    		if (this._mode == Ametys.form.field.Password.MODE_SEEPASSWORD)
+			{
+				this._field.disable();
+				this._button.setTooltip(Ametys.form.field.Password.CHANGE_PASSWORD_TEXT);
+				this._button.setIcon(Ametys.form.field.Password.CHANGE_PASSWORD_ICON_PATH);
+			}
+    		else
+			{
+    			// The user is modifying the value
+    			this._field.enable();
+				this._button.setTooltip(Ametys.form.field.Password.RESET_PASSWORD_TEXT);
+				this._button.setIcon(Ametys.form.field.Password.RESET_PASSWORD_ICON_PATH);
+			}
+
+    		this._button.show();
+		}
+    }
+});
Index: main/plugin-core-ui/resources/js/Ametys/form/field/StringTime.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/form/field/StringTime.js	(revision 0)
+++ main/plugin-core-ui/resources/js/Ametys/form/field/StringTime.js	(revision 0)
@@ -0,0 +1,84 @@
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+/**
+ * This abstract class is used by widgets providing a drop-down menu listing times. Its value is stored as a String.
+ */
+Ext.define('Ametys.form.widget.StringTime', {
+    
+    extend: 'Ametys.form.AbstractFieldsWrapper',
+    alias: ['widget.stringtimefield', 'widget.stringtime'],
+    
+    /**
+     * @cfg {String} format the disply format. Can be "VeryShortTime" to have hours and minutes or "ShortTime" to have hours, minutes and seconds
+     * Defaults to "ShortTime" 
+     */
+    format: "ShortTime",
+    
+    /**
+	 * @cfg {Object} timeConfig A configuration to transmit to the time field. See {@link Ext.form.field.Time}
+	 */
+    
+    /**
+     * @property {Ext.form.field.Time} _timeField the time field
+     */
+    
+    initComponent: function()
+    {
+        this.items = this._getItems();
+        this.callParent();
+    },
+	
+    /**
+     * @private
+     * Get the items composing the fields
+     * @return {Object[]} The items
+     */
+    _getItems: function ()
+    {
+    	this._timeField = Ext.create('Ext.form.field.Time', Ext.applyIf(this.timeConfig|| {}, this._getTimeFieldConfig()));
+    	return [this._timeField];
+    },
+    
+    /**
+     * @private
+     * Get the time field's configuration
+     * @return {Object} the time field's configuration
+     */
+    _getTimeFieldConfig: function ()
+    {
+        return {
+            format: this.format == "ShortTime" ? "H:i" : "H:i:s",
+    		value: this.value == Ametys.form.widget.Date.CURRENT ? new Date() : this.value,
+        };
+    },
+    
+	getValue: function()
+	{
+		return Ext.Date.format(this._timeField.getValue(), this._timeField.format);
+	},
+	
+	setValue: function(value)
+	{
+		this._timeField.setValue(value);
+	},
+	
+    getErrors: function (value) 
+    {
+    	var errors = this._timeField.getErrors(value);
+    	return errors;
+    }
+});
Index: main/plugin-core-ui/resources/js/Ametys/helper/SaveErrorDialog.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/helper/SaveErrorDialog.js	(revision 32647)
+++ main/plugin-core-ui/resources/js/Ametys/helper/SaveErrorDialog.js	(nonexistent)
@@ -1,92 +0,0 @@
-/*
- *  Copyright 2013 Anyware Services
- *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-/**
- * This UI helper provides a dialog box to display errors while saving
- * See #showErrorDialog method
- */
-Ext.define('Ametys.helper.SaveErrorDialog', {
-	singleton: true,
-	
-	/**
-	 * @private
-	 * @property {Boolean} _initialized Is the dialog already initialized?
-	 */
-	_initialized: false,
-	
-	/**
-	 * Show the error dialog box
-	 * @param {String} title The dialog box's title. Can be null to have a default 'save' title
-	 * @param {String} msg The dialog box's main message
-	 * @param {String} detailedMsg The detailed message (scrolling if too large)
-	 */
-	showErrorDialog: function (title, msg, detailedMsg)
-	{
-		this._delayedInitialized();
-		this._errorDialog.setTitle (title || "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_ACTION_FAILED_TITLE'/>");
-		
-		this._errorDialog.items.get(0).update(msg);
-		this._errorDialog.items.get(1).update(detailedMsg);
-		
-		this._errorDialog.show();
-	},
-	
-	/**
-	 * @private
-	 * Initialize the error dialog box
-	 */
-	_delayedInitialized: function ()
-	{
-		if (!this._initialized)
-		{
-			this._errorDialog = Ext.create('Ametys.window.DialogBox', {
-		    	
-		    	title: "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_ACTION_FAILED_TITLE'/>",
-		    	icon: Ametys.getPluginResourcesPrefix('core-ui') + "/img/error_16.gif",
-		    	
-				width: 450,
-				maxHeight: 280,
-				layout: 'vbox',
-					
-				items : [ {
-						xtype: 'container',
-						cls: 'save-error-dialog-text',
-						width: '100%',
-						html: ''
-					},
-					{
-						xtype: 'container',
-						cls: 'save-error-dialog-details',
-						flex: 1,
-						width: '100%',
-						autoScroll: true,
-						html: ''
-					}
-				],
-							
-				closeAction: 'hide',
-				buttons : [ {
-						text : "<i18n:text i18n:key='PLUGINS_CORE_UI_SAVE_ACTION_FAILED_OK_BTN'/>",
-						handler : function () { this._errorDialog.close()},
-						scope: this
-					}
-				]
-			});
-
-			this._initialized = true;
-		}
-	}
-});
\ No newline at end of file
Index: main/plugin-core-ui/resources/js/Ametys/plugins/coreui/configurableformpanel/TestsController.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/plugins/coreui/configurableformpanel/TestsController.js	(revision 32647)
+++ main/plugin-core-ui/resources/js/Ametys/plugins/coreui/configurableformpanel/TestsController.js	(working copy)
@@ -15,7 +15,7 @@
  */
 
 /**
- * The controller to launche tests on a configurable form panel
+ * The controller launching tests on a configurable form panel
  * @private
  */
 Ext.define('Ametys.plugins.coreui.configurableformpanel.TestsController', {
@@ -32,7 +32,7 @@
             if (target != null)
             {
                 var mode = controller.getInitialConfig().mode;
-                var form = target.getParameters().form;
+                var form = target.getParameters().object.owner;
                 
                 var activeParamCheckers = form._paramCheckersDAO._paramCheckers.filter(function (el) { return el.isActive; });
                 form._paramCheckersDAO.check(activeParamCheckers, true, Ext.emptyFn , mode == 'all');
@@ -44,7 +44,6 @@
 	 * @property {String} _mode the check mode (can be "all" or "missed")
 	 * @private
 	 */
-	
 	constructor: function(config)
 	{
 		this.callParent(arguments);
Index: main/plugin-core-ui/resources/js/Ametys/plugins/coreui/userprefs/UserPrefsActions.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/plugins/coreui/userprefs/UserPrefsActions.js	(revision 32647)
+++ main/plugin-core-ui/resources/js/Ametys/plugins/coreui/userprefs/UserPrefsActions.js	(working copy)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2013 Anyware Services
+ *  Copyright 2015 Anyware Services
  *
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -202,19 +202,7 @@
 	 */
 	_saveUserPrefs: function ()
 	{
-		var invalidFields = this._form.getInvalidFields();
-		if (invalidFields.length > 0)
-		{
-			Ametys.Msg.show({
-				   title: "<i18n:text i18n:key='PLUGINS_CORE_UI_USER_PREFERENCES_SAVE'/>",
-				   msg: "<i18n:text i18n:key='PLUGINS_CORE_UI_USER_PREFERENCES_SAVE_INVALIDFIELDS'/>" + invalidFields.join(', '),
-				   buttons: Ext.Msg.OK,
-				   icon: Ext.MessageBox.ERROR
-			});
-			return;
-		}
-		
-		Ametys.userprefs.UserPrefsDAO.saveValues(this._form.getValues(), Ext.bind(this._saveCb, this));
+        Ametys.form.SaveHelper.canSave(this._form, Ext.bind(Ametys.userprefs.UserPrefsDAO.saveValues, Ametys.userprefs.UserPrefsDAO,[this._form.getValues(), Ext.bind(this._saveCb, this)], 1));
 	},
 	
 	/**
@@ -227,18 +215,10 @@
 	{
 		if (success == false && errors != null)
     	{
-    		var details = "";
-    		for (var error in errors)
-    		{
-    			details += error + ": " + errors[error] + "\n";
-    		}
-    		
-    	    Ametys.log.ErrorDialog.display({
-    	        title: "<i18n:text i18n:key='PLUGINS_CORE_UI_USER_PREFERENCES_RESET_WORKSPACE_FAILURE_TITLE'/>",
-    	        text: "<i18n:text i18n:key='PLUGINS_CORE_UI_USER_PREFERENCES_RESET_WORKSPACE_FAILURE_DESC'/>",
-    	        details: details,
-    	        category: this.self.getName() 
-    	    });
+			Ametys.form.SaveHelper.handleServerErrors(this._form, 
+					"<i18n:text i18n:key='PLUGINS_CORE_UI_USER_PREFERENCES_RESET_WORKSPACE_FAILURE_TITLE'/>", 
+					"<i18n:text i18n:key='PLUGINS_CORE_UI_USER_PREFERENCES_RESET_WORKSPACE_FAILURE_DESC'/>", 
+					errors);
     	}
     	else
     	{
Index: main/plugin-core-ui/resources/js/Ametys/ribbon/element/ui/CommonController.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/ribbon/element/ui/CommonController.js	(revision 32647)
+++ main/plugin-core-ui/resources/js/Ametys/ribbon/element/ui/CommonController.js	(working copy)
@@ -974,7 +974,7 @@
 				// nomatch
 				this.setAdditionalDescription(this.getInitialConfig("selection-description-nomatch"));
 			}
-			else if (!this._hasRightOnAny(targets))
+			else if (!this.hasRightOnAny(targets))
 			{
 				// noright
 				this.setAdditionalDescription(this.getInitialConfig("rights-description-no") || this.getInitialConfig("no-right-description"));
@@ -1236,7 +1236,7 @@
 				Ext.Object.each(target.getParameters(), function(key, value, parameters) {
 					if ((!selectionParameterNameReverseRegexp && selectionParameterNameRegexp.test(key)
 							|| selectionParameterNameReverseRegexp && !selectionParameterNameRegexp.test(key))
-						&& checkValue(value))
+						&& value != null && checkValue(value))
 					{
 						gotOne = true;
 						return false; // stop the iteration
@@ -1313,9 +1313,9 @@
 		/**
 		 * Checks if the rights on at least one target is ok for at least one right required for the button.
 		 * @param {Ametys.message.MessageTarget[]} targets The message targets to check
-		 * @private
+		 * @protected
 		 */
-		_hasRightOnAny: function(targets)
+		hasRightOnAny: function(targets)
 		{
 			var rightToCheck = this.getInitialConfig("rights");
 			
Index: main/plugin-core-ui/resources/js/Ametys/userprefs/UserPrefsDAO.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/userprefs/UserPrefsDAO.js	(revision 32647)
+++ main/plugin-core-ui/resources/js/Ametys/userprefs/UserPrefsDAO.js	(working copy)
@@ -229,8 +229,13 @@
 	 * @param {Number} [priority] The priority of the Ametys.data.ServerComm#send call: default is Ametys.data.ServerComm#PRIORITY_MAJOR. Set to Ametys.data.ServerComm#PRIORITY_MINOR to save less important preferences. 
 	 * @param {String} [cancelCode] A cancel code to prevent many successive save process. See Ametys.data.ServerComm#send for more information.   
 	 */
-	saveValues: function(params, callback, prefContext, priority, cancelCode)
+	saveValues: function(canSave, params, callback, prefContext, priority, cancelCode)
 	{
+		if (!canSave)
+		{
+			return;
+		}
+		
 		params["prefContext"] = prefContext || this._defaultPrefContext;
 		params["submit"] = 'true';
 		
Index: main/plugin-core-ui/resources/js/Ametys/userprefs/UserPrefsDAOStateProvider.js
===================================================================
--- main/plugin-core-ui/resources/js/Ametys/userprefs/UserPrefsDAOStateProvider.js	(revision 32647)
+++ main/plugin-core-ui/resources/js/Ametys/userprefs/UserPrefsDAOStateProvider.js	(working copy)
@@ -86,7 +86,7 @@
     	var save = {};
     	save[this.preference] = this.encodeValue(this.state);
     	
-    	Ametys.userprefs.UserPrefsDAO.saveValues(save, Ext.bind(this._saveStateCB, this), this.prefContext, priority, this.self.getName() + "$saving");
+    	Ametys.userprefs.UserPrefsDAO.saveValues(true, save, Ext.bind(this._saveStateCB, this), this.prefContext, priority, this.self.getName() + "$saving");
     },
     
     /**
Index: main/plugin-core-ui/resources/js/Ext.enhancements.js
===================================================================
--- main/plugin-core-ui/resources/js/Ext.enhancements.js	(revision 32913)
+++ main/plugin-core-ui/resources/js/Ext.enhancements.js	(working copy)
@@ -681,9 +681,9 @@
                 width -= owner.commentWrapEl.getWidth();
             }
             
-            if (owner.warnWrapEl && owner.hasActiveWarning()) 
+            if (owner.warningWrapEl && owner.hasActiveWarning()) 
             {
-                width -= owner.warnWrapEl.getWidth();
+                width -= owner.warningWrapEl.getWidth();
             }
             
             this.callParent([ownerContext, width]);
Index: main/plugin-core-ui/stylesheets/kernel.xsl
===================================================================
--- main/plugin-core-ui/stylesheets/kernel.xsl	(revision 32758)
+++ main/plugin-core-ui/stylesheets/kernel.xsl	(working copy)
@@ -129,6 +129,8 @@
 	        <script>/plugins/core-ui/resources/js/Ametys/form/AbstractField.js</script>
 	        <script>/plugins/core-ui/resources/js/Ametys/form/AbstractFieldsWrapper.js</script>
 	        <script>/plugins/core-ui/resources/js/Ametys/form/field/DateTime.js</script>
+	        <script>/plugins/core-ui/resources/js/Ametys/form/field/StringTime.js</script>
+	        <script>/plugins/core-ui/resources/js/Ametys/form/field/Password.js</script>
 	        <script>/plugins/core-ui/resources/js/Ametys/form/field/ChangePassword.js</script>
             <script>/plugins/core-ui/resources/js/Ametys/form/field/ReferencedNumberField.js</script>
             <script>/plugins/core-ui/resources/js/Ametys/form/field/RichText.js</script>
